diff --git a/.gitignore b/.gitignore
index d3452d081..bfbb07022 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,3 +62,9 @@ lib/i18n/generated/
# Widget Preview related
.widget_preview/
+
+# Unused platforms
+macos/*
+linux/*
+windows/*
+web/*
diff --git a/android/app/src/main/res/values-hi/widget_plurals.xml b/android/app/src/main/res/values-hi/widget_plurals.xml
index 20f3be92f..cc198aa36 100644
--- a/android/app/src/main/res/values-hi/widget_plurals.xml
+++ b/android/app/src/main/res/values-hi/widget_plurals.xml
@@ -4,8 +4,20 @@
- %d दिनों के लिए HRT पर
- %d दिनों के लिए HRT पर
+
+ - HRT on %d week
+ - HRT on %d week
+
- %d महीनों के लिए HRT पर
- %d महीनों के लिए HRT पर
+
+ - HRT पर %d वर्ष
+ - HRT पर %d वर्ष
+
+
+ - %d प्रवेश लॉग इन
+ - %d प्रवेश लॉग इन
+
diff --git a/android/app/src/main/res/values-hi/widget_strings.xml b/android/app/src/main/res/values-hi/widget_strings.xml
new file mode 100644
index 000000000..453a446e8
--- /dev/null
+++ b/android/app/src/main/res/values-hi/widget_strings.xml
@@ -0,0 +1,8 @@
+
+
+ HRT पर समय
+ जब तक आप एचआरटी और आपके कुल सेवन पर रहे हैं, तब तक दिखाएं
+ अपना पहला सेवन लॉग इन करने के लिए मोना खोलें
+ 8 महीने के लिए HRT पर
+ 16 सेवन लॉग इन
+
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index a59f7475a..ed1cc185a 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
index 03812c49e..475254424 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
index e715b4e56..9ddc57d71 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
new file mode 100644
index 000000000..c35a190d6
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1.png
new file mode 100644
index 000000000..4a26acc26
Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2.png
new file mode 100644
index 000000000..1f00ee571
Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3.png
new file mode 100644
index 000000000..2882acca7
Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4.png
new file mode 100644
index 000000000..0e2aa10e2
Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/hi-IN/full_description.txt b/fastlane/metadata/android/hi-IN/full_description.txt
new file mode 100644
index 000000000..c187a8c00
--- /dev/null
+++ b/fastlane/metadata/android/hi-IN/full_description.txt
@@ -0,0 +1,22 @@
+मोना के साथ अपने हार्मोनल स्वास्थ्य का नियंत्रण करें, पूरी तरह से स्थानीय, ओपन सोर्स ऐप आपको अपने हार्मोन यात्रा को ट्रैक करने, समझने और प्रबंधित करने में मदद करने के लिए डिज़ाइन किया गया है।
+
+क्यों मोना अलग है:
+
+- पूरी तरह से निजी: आपके सभी डेटा आपके डिवाइस पर रहते हैं। सर्वर पर कुछ भी नहीं भेजा जाता है।
+- ओपन सोर्स: ऐप पूरी तरह से खुला स्रोत है, जो समुदाय से अधिकतम पारदर्शिता और योगदान की अनुमति देता है।
+- जीवन के लिए नि: शुल्क: कोई सदस्यता, कोई प्रीमियम सुविधाएँ नहीं। मोना हर किसी के लिए स्वतंत्र है।
+
+दवा अनुस्मारक:
+
+- अपनी दवा रेजिमेंट जोड़ें
+प्रत्येक आगामी खुराक (मात्रा, तारीख, पक्ष, मिस्ड खुराक) के लिए विस्तृत जानकारी प्राप्त करें
+- अधिसूचना प्राप्त करें ताकि आप कभी भी खुराक नहीं छोड़ सकें
+
+अपनी दवा के स्टॉक को कुशलतापूर्वक प्रबंधित करें:
+
+- स्टॉक में प्रत्येक दवा का ट्रैक रखें
+- प्रत्येक सेवन के साथ क्वांटिटी स्वचालित रूप से अपडेट की जाती है
+
+हार्मोन का स्तर ग्राफ:
+
+- एस्ट्राडियोल मोनोथेरेपी पर उपयोगकर्ताओं के लिए, समय के साथ नकली हार्मोन स्तर को दृश्यित करें
diff --git a/fastlane/metadata/android/hi-IN/short_description.txt b/fastlane/metadata/android/hi-IN/short_description.txt
new file mode 100644
index 000000000..5bca09a5c
--- /dev/null
+++ b/fastlane/metadata/android/hi-IN/short_description.txt
@@ -0,0 +1 @@
+आपका ट्रांस हेल्थ साथी
diff --git a/fastlane/metadata/android/hi-IN/title.txt b/fastlane/metadata/android/hi-IN/title.txt
new file mode 100644
index 000000000..286eee73b
--- /dev/null
+++ b/fastlane/metadata/android/hi-IN/title.txt
@@ -0,0 +1 @@
+मोना - HRT जर्नल
diff --git a/fastlane/metadata/android/hu-HU/title.txt b/fastlane/metadata/android/hu-HU/title.txt
new file mode 100644
index 000000000..ccde3b94c
--- /dev/null
+++ b/fastlane/metadata/android/hu-HU/title.txt
@@ -0,0 +1 @@
+Mona - HRT napló
diff --git a/fastlane/metadata/ios/hi/description.txt b/fastlane/metadata/ios/hi/description.txt
new file mode 100644
index 000000000..c187a8c00
--- /dev/null
+++ b/fastlane/metadata/ios/hi/description.txt
@@ -0,0 +1,22 @@
+मोना के साथ अपने हार्मोनल स्वास्थ्य का नियंत्रण करें, पूरी तरह से स्थानीय, ओपन सोर्स ऐप आपको अपने हार्मोन यात्रा को ट्रैक करने, समझने और प्रबंधित करने में मदद करने के लिए डिज़ाइन किया गया है।
+
+क्यों मोना अलग है:
+
+- पूरी तरह से निजी: आपके सभी डेटा आपके डिवाइस पर रहते हैं। सर्वर पर कुछ भी नहीं भेजा जाता है।
+- ओपन सोर्स: ऐप पूरी तरह से खुला स्रोत है, जो समुदाय से अधिकतम पारदर्शिता और योगदान की अनुमति देता है।
+- जीवन के लिए नि: शुल्क: कोई सदस्यता, कोई प्रीमियम सुविधाएँ नहीं। मोना हर किसी के लिए स्वतंत्र है।
+
+दवा अनुस्मारक:
+
+- अपनी दवा रेजिमेंट जोड़ें
+प्रत्येक आगामी खुराक (मात्रा, तारीख, पक्ष, मिस्ड खुराक) के लिए विस्तृत जानकारी प्राप्त करें
+- अधिसूचना प्राप्त करें ताकि आप कभी भी खुराक नहीं छोड़ सकें
+
+अपनी दवा के स्टॉक को कुशलतापूर्वक प्रबंधित करें:
+
+- स्टॉक में प्रत्येक दवा का ट्रैक रखें
+- प्रत्येक सेवन के साथ क्वांटिटी स्वचालित रूप से अपडेट की जाती है
+
+हार्मोन का स्तर ग्राफ:
+
+- एस्ट्राडियोल मोनोथेरेपी पर उपयोगकर्ताओं के लिए, समय के साथ नकली हार्मोन स्तर को दृश्यित करें
diff --git a/fastlane/metadata/ios/hi/keywords.txt b/fastlane/metadata/ios/hi/keywords.txt
new file mode 100644
index 000000000..f6f95f0f6
--- /dev/null
+++ b/fastlane/metadata/ios/hi/keywords.txt
@@ -0,0 +1 @@
+मोना, ट्रांस, ट्रांसजेंडर, हार्मोन, हार्मोनल, ट्रैकर, लॉग, hrt, पत्रिका, मेमो, एस्ट्राडियोल, सिम्युलेटर
diff --git a/fastlane/metadata/ios/hi/name.txt b/fastlane/metadata/ios/hi/name.txt
new file mode 100644
index 000000000..286eee73b
--- /dev/null
+++ b/fastlane/metadata/ios/hi/name.txt
@@ -0,0 +1 @@
+मोना - HRT जर्नल
diff --git a/fastlane/metadata/ios/hi/promotional_text.txt b/fastlane/metadata/ios/hi/promotional_text.txt
new file mode 100644
index 000000000..b505ed0a4
--- /dev/null
+++ b/fastlane/metadata/ios/hi/promotional_text.txt
@@ -0,0 +1 @@
+अपने हार्मोन ट्रैक करें। अपने meds प्रबंधित करें। सभी निजी, खुला और मुक्त।।
diff --git a/fastlane/metadata/ios/hi/subtitle.txt b/fastlane/metadata/ios/hi/subtitle.txt
new file mode 100644
index 000000000..5bca09a5c
--- /dev/null
+++ b/fastlane/metadata/ios/hi/subtitle.txt
@@ -0,0 +1 @@
+आपका ट्रांस हेल्थ साथी
diff --git a/fastlane/metadata/ios/hu/keywords.txt b/fastlane/metadata/ios/hu/keywords.txt
new file mode 100644
index 000000000..26e61c02a
--- /dev/null
+++ b/fastlane/metadata/ios/hu/keywords.txt
@@ -0,0 +1 @@
+mona, transz, tansznemű, hormon, hormonális, követő, napló, hrt, napló, memó, estradiol, szimulátor
diff --git a/fastlane/metadata/ios/hu/name.txt b/fastlane/metadata/ios/hu/name.txt
new file mode 100644
index 000000000..ccde3b94c
--- /dev/null
+++ b/fastlane/metadata/ios/hu/name.txt
@@ -0,0 +1 @@
+Mona - HRT napló
diff --git a/fastlane/screenshot_captions/en.json b/fastlane/screenshot_captions/en.json
new file mode 100644
index 000000000..631faff28
--- /dev/null
+++ b/fastlane/screenshot_captions/en.json
@@ -0,0 +1,6 @@
+{
+ "1": "Never miss a dose",
+ "2": "Monitor your levels and blood tests",
+ "3": "See your history at a glance",
+ "4": "Never run out"
+}
\ No newline at end of file
diff --git a/fastlane/screenshot_captions/fr.json b/fastlane/screenshot_captions/fr.json
new file mode 100644
index 000000000..32a584990
--- /dev/null
+++ b/fastlane/screenshot_captions/fr.json
@@ -0,0 +1,6 @@
+{
+ "1": "Ne ratez jamais une prise",
+ "2": "Suivez vos taux et prises de sang",
+ "3": "Votre historique à portée de main",
+ "4": "Ne manquez jamais de stock"
+}
\ No newline at end of file
diff --git a/fastlane/screenshot_locales.json b/fastlane/screenshot_locales.json
new file mode 100644
index 000000000..eed3b74e9
--- /dev/null
+++ b/fastlane/screenshot_locales.json
@@ -0,0 +1,4 @@
+[
+ { "app": "en", "play": "en-US", "appstore": "en-US" },
+ { "app": "fr", "play": "fr-FR", "appstore": "fr-FR" }
+]
diff --git a/fastlane/screenshots/en-US/1.png b/fastlane/screenshots/en-US/1.png
new file mode 100644
index 000000000..6880df98a
Binary files /dev/null and b/fastlane/screenshots/en-US/1.png differ
diff --git a/fastlane/screenshots/en-US/2.png b/fastlane/screenshots/en-US/2.png
new file mode 100644
index 000000000..74151da6d
Binary files /dev/null and b/fastlane/screenshots/en-US/2.png differ
diff --git a/fastlane/screenshots/en-US/3.png b/fastlane/screenshots/en-US/3.png
new file mode 100644
index 000000000..271532df7
Binary files /dev/null and b/fastlane/screenshots/en-US/3.png differ
diff --git a/fastlane/screenshots/en-US/4.png b/fastlane/screenshots/en-US/4.png
new file mode 100644
index 000000000..adfea1357
Binary files /dev/null and b/fastlane/screenshots/en-US/4.png differ
diff --git a/fastlane/screenshots/fr-FR/1.png b/fastlane/screenshots/fr-FR/1.png
new file mode 100644
index 000000000..075a8facf
Binary files /dev/null and b/fastlane/screenshots/fr-FR/1.png differ
diff --git a/fastlane/screenshots/fr-FR/2.png b/fastlane/screenshots/fr-FR/2.png
new file mode 100644
index 000000000..c36e37050
Binary files /dev/null and b/fastlane/screenshots/fr-FR/2.png differ
diff --git a/fastlane/screenshots/fr-FR/3.png b/fastlane/screenshots/fr-FR/3.png
new file mode 100644
index 000000000..b157a2c37
Binary files /dev/null and b/fastlane/screenshots/fr-FR/3.png differ
diff --git a/fastlane/screenshots/fr-FR/4.png b/fastlane/screenshots/fr-FR/4.png
new file mode 100644
index 000000000..cd7aae480
Binary files /dev/null and b/fastlane/screenshots/fr-FR/4.png differ
diff --git a/lib/app.dart b/lib/app.dart
index 86bec1e53..05bff68e3 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:mona/controllers/notification_planner.dart';
import 'package:mona/controllers/notification_scheduler.dart';
+import 'package:mona/controllers/slot_finder.dart';
+import 'package:mona/controllers/slots_builder.dart';
import 'package:mona/data/providers/medication_intake_provider.dart';
import 'package:mona/data/providers/medication_schedule_provider.dart';
import 'package:mona/i18n/build_context_extensions.dart';
@@ -14,9 +16,13 @@ import 'package:mona/services/notification_service.dart';
import 'package:mona/services/preferences_service.dart';
import 'package:mona/theme/app_theme_controller.dart';
import 'package:mona/theme/material_ui_theme.dart';
+import 'package:mona/ui/views/home/take_medication_page.dart';
import 'package:provider/provider.dart';
import 'ui/views/main_page.dart';
+const bool _forceDefaultColors = bool.fromEnvironment(
+ 'SCREENSHOT_DEFAULT_COLORS'); // avoid dynamic colors when generating store screenshots
+
class MonaApp extends StatefulWidget {
const MonaApp({super.key});
@@ -25,7 +31,9 @@ class MonaApp extends StatefulWidget {
}
class _MonaAppState extends State with WidgetsBindingObserver {
+ final GlobalKey _navigatorKey = GlobalKey();
String? _lastTimeZone;
+ NotificationPayload? _pendingLaunchTap;
late MedicationScheduleProvider _medicationScheduleProvider;
late MedicationIntakeProvider _medicationIntakeProvider;
late PreferencesService _preferencesService;
@@ -41,7 +49,7 @@ class _MonaAppState extends State with WidgetsBindingObserver {
_lastTimeZone = clock.now().timeZoneOffset.toString();
WidgetsBinding.instance.addPostFrameCallback((_) async {
- await NotificationService().initialize();
+ await NotificationService().initialize(onTap: _handleNotificationTap);
if (!mounted) return;
_medicationScheduleProvider = context.read();
_medicationIntakeProvider = context.read();
@@ -62,6 +70,11 @@ class _MonaAppState extends State with WidgetsBindingObserver {
_medicationIntakeProvider.addListener(_regenerateHomeWidget);
_localeProvider.addListener(_regenerateHomeWidget);
_regenerateHomeWidget();
+
+ _medicationScheduleProvider.addListener(_routeLaunchTap);
+ _medicationIntakeProvider.addListener(_routeLaunchTap);
+ _pendingLaunchTap = await NotificationService().getAppLaunchTap();
+ _routeLaunchTap();
});
}
@@ -74,6 +87,8 @@ class _MonaAppState extends State with WidgetsBindingObserver {
_preferencesService.removeListener(_regenerateNotifications);
_medicationIntakeProvider.removeListener(_regenerateHomeWidget);
_localeProvider.removeListener(_regenerateHomeWidget);
+ _medicationScheduleProvider.removeListener(_routeLaunchTap);
+ _medicationIntakeProvider.removeListener(_routeLaunchTap);
}
super.dispose();
}
@@ -94,6 +109,42 @@ class _MonaAppState extends State with WidgetsBindingObserver {
);
}
+ void _routeLaunchTap() {
+ final tap = _pendingLaunchTap;
+ if (tap == null || !_initialized || !mounted) return;
+ if (_medicationScheduleProvider.isLoading ||
+ _medicationIntakeProvider.isLoading) {
+ return;
+ }
+
+ _pendingLaunchTap = null;
+ _handleNotificationTap(tap);
+ }
+
+ void _handleNotificationTap(NotificationPayload payload) {
+ if (!_initialized || !mounted) return;
+
+ final slots = SlotsBuilder(
+ _medicationIntakeProvider,
+ _medicationScheduleProvider,
+ ).intakeSlots();
+ final target = findSlot(payload.scheduleId, payload.scheduledTime, slots);
+
+ if (target != null) {
+ _navigatorKey.currentState?.push(
+ MaterialPageRoute(
+ fullscreenDialog: true,
+ builder: (_) =>
+ TakeMedicationPage(target.schedule, scheduledTime: target.time),
+ ),
+ );
+ }
+
+ final notificationId = payload.notificationId;
+ if (notificationId != null) NotificationService().cancel(notificationId);
+ if (payload.isRepeating) _regenerateNotifications();
+ }
+
void _checkTimezoneChange() {
final currentTimezone = clock.now().timeZoneOffset.toString();
if (_lastTimeZone != currentTimezone) {
@@ -117,10 +168,11 @@ class _MonaAppState extends State with WidgetsBindingObserver {
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
final themes = context.read().buildThemeData(
- systemLight: lightDynamic,
- systemDark: darkDynamic,
+ systemLight: _forceDefaultColors ? null : lightDynamic,
+ systemDark: _forceDefaultColors ? null : darkDynamic,
);
return MaterialApp(
+ navigatorKey: _navigatorKey,
title: 'Mona',
locale: context.watch().locale,
supportedLocales: context.watch().supportedLocales,
diff --git a/lib/controllers/medication_intake_manager.dart b/lib/controllers/medication_intake_manager.dart
index e0d871c55..761f30977 100644
--- a/lib/controllers/medication_intake_manager.dart
+++ b/lib/controllers/medication_intake_manager.dart
@@ -3,7 +3,9 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:mona/controllers/supply_item_manager.dart';
+import 'package:mona/data/model/administration_route.dart';
import 'package:mona/data/model/generic_supply_item.dart';
+import 'package:mona/data/model/injection_type.dart';
import 'package:mona/data/model/medication_schedule.dart';
import 'package:mona/data/model/medication_supply_item.dart';
import 'package:mona/data/model/placement.dart';
@@ -33,6 +35,7 @@ class MedicationIntakeManager {
String? notes,
Decimal? wastedAmount, // in mL
List placements = const [],
+ InjectionType? injectionType,
}) async {
if (!takenDateTime.isUtc) {
throw ArgumentError('takenDateTime must be in UTC');
@@ -57,6 +60,7 @@ class MedicationIntakeManager {
deadSpace: deadSpace,
placements: placements,
dosingBasis: schedule.dosingBasis,
+ injectionType: injectionType,
));
final itemManager = SupplyItemManager(_supplyItemProvider);
@@ -111,6 +115,7 @@ class MedicationIntakeManager {
List genericItems = const [],
String? notes,
List placements = const [],
+ InjectionType? injectionType,
}) async {
if (!takenDateTime.isUtc) {
throw ArgumentError('takenDateTime must be in UTC');
@@ -173,6 +178,7 @@ class MedicationIntakeManager {
genericSupplyItemIds: genericItems.map((item) => item.id).toList(),
notes: notes,
placements: placements,
+ injectionType: injectionType,
));
}
@@ -205,6 +211,18 @@ class MedicationIntakeManager {
Placement? suggestNextPlacement({required int scheduleId}) =>
getOrderedPlacements(scheduleId: scheduleId).firstOrNull;
+ InjectionType? suggestInjectionType({required int scheduleId}) {
+ final lastIntake =
+ _medicationIntakeProvider.getLastTakenIntakeForSchedule(scheduleId);
+
+ if (lastIntake == null ||
+ lastIntake.administrationRoute != AdministrationRoute.injection) {
+ return null;
+ }
+
+ return lastIntake.injectionType;
+ }
+
MedicationSupplyItem? suggestMedicationItem({
required MedicationSchedule schedule,
}) {
diff --git a/lib/controllers/notification_scheduler.dart b/lib/controllers/notification_scheduler.dart
index 78d3215e4..0df655499 100644
--- a/lib/controllers/notification_scheduler.dart
+++ b/lib/controllers/notification_scheduler.dart
@@ -54,6 +54,7 @@ class NotificationScheduler {
final dateFormat = DateFormat.MMMMd(localeName);
return NotificationService().scheduleNotification(
id: _notificationIdFor(plan.schedule.id, plan.dateTime),
+ scheduleId: plan.schedule.id,
title: t.notificationMedicationReminderTitle(
scheduleName: plan.schedule.name),
body: t.notificationMedicationReminderBodyDate(
@@ -70,6 +71,7 @@ class NotificationScheduler {
final timeFormat = DateFormat.Hm(localeName);
return NotificationService().scheduleDailyNotification(
id: _notificationIdFor(plan.schedule.id, plan.firstFire),
+ scheduleId: plan.schedule.id,
title: t.notificationMedicationReminderTitle(
scheduleName: plan.schedule.name),
body: t.notificationMedicationReminderBodyTime(
@@ -86,6 +88,7 @@ class NotificationScheduler {
final weekdayFormat = DateFormat.EEEE(localeName);
return NotificationService().scheduleWeeklyNotification(
id: _notificationIdFor(plan.schedule.id, plan.firstFire),
+ scheduleId: plan.schedule.id,
title: t.notificationMedicationReminderTitle(
scheduleName: plan.schedule.name),
body: t.notificationMedicationReminderBodyWeekday(
diff --git a/lib/controllers/slot_finder.dart b/lib/controllers/slot_finder.dart
new file mode 100644
index 000000000..3cdd69cf3
--- /dev/null
+++ b/lib/controllers/slot_finder.dart
@@ -0,0 +1,22 @@
+import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:mona/data/model/intake_slot.dart';
+
+IntakeSlot? findSlot(
+ int scheduleId,
+ DateTime scheduledTime,
+ List slots,
+) {
+ final targetSlots =
+ slots.where((slot) => slot.schedule.id == scheduleId).toList();
+ if (targetSlots.isEmpty) return null;
+
+ final slot = targetSlots.length == 1
+ ? targetSlots.single
+ : targetSlots.firstWhereOrNull(
+ (slot) => slot.time == TimeOfDay.fromDateTime(scheduledTime));
+ if (slot == null) return null;
+ if (slot.intake != null) return null;
+
+ return slot;
+}
diff --git a/lib/data/model/injection_type.dart b/lib/data/model/injection_type.dart
new file mode 100644
index 000000000..baf5c92d9
--- /dev/null
+++ b/lib/data/model/injection_type.dart
@@ -0,0 +1,9 @@
+import 'package:dart_mappable/dart_mappable.dart';
+
+part 'injection_type.mapper.dart';
+
+@MappableEnum()
+enum InjectionType {
+ intramuscular,
+ subcutaneous,
+}
diff --git a/lib/data/model/injection_type.mapper.dart b/lib/data/model/injection_type.mapper.dart
new file mode 100644
index 000000000..27e4f3526
--- /dev/null
+++ b/lib/data/model/injection_type.mapper.dart
@@ -0,0 +1,55 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// dart format off
+// ignore_for_file: type=lint
+// ignore_for_file: invalid_use_of_protected_member
+// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
+// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
+
+part of 'injection_type.dart';
+
+class InjectionTypeMapper extends EnumMapper {
+ InjectionTypeMapper._();
+
+ static InjectionTypeMapper? _instance;
+ static InjectionTypeMapper ensureInitialized() {
+ if (_instance == null) {
+ MapperContainer.globals.use(_instance = InjectionTypeMapper._());
+ }
+ return _instance!;
+ }
+
+ static InjectionType fromValue(dynamic value) {
+ ensureInitialized();
+ return MapperContainer.globals.fromValue(value);
+ }
+
+ @override
+ InjectionType decode(dynamic value) {
+ switch (value) {
+ case r'intramuscular':
+ return InjectionType.intramuscular;
+ case r'subcutaneous':
+ return InjectionType.subcutaneous;
+ default:
+ throw MapperException.unknownEnumValue(value);
+ }
+ }
+
+ @override
+ dynamic encode(InjectionType self) {
+ switch (self) {
+ case InjectionType.intramuscular:
+ return r'intramuscular';
+ case InjectionType.subcutaneous:
+ return r'subcutaneous';
+ }
+ }
+}
+
+extension InjectionTypeMapperExtension on InjectionType {
+ String toValue() {
+ InjectionTypeMapper.ensureInitialized();
+ return MapperContainer.globals.toValue(this) as String;
+ }
+}
diff --git a/lib/data/model/medication_intake.dart b/lib/data/model/medication_intake.dart
index 47b4229aa..04276278a 100644
--- a/lib/data/model/medication_intake.dart
+++ b/lib/data/model/medication_intake.dart
@@ -7,6 +7,7 @@ import 'package:mona/data/model/custom_mappers.dart';
import 'package:mona/data/model/date.dart';
import 'package:mona/data/model/dosing_basis.dart';
import 'package:mona/data/model/ester.dart';
+import 'package:mona/data/model/injection_type.dart';
import 'package:mona/data/model/mapping_hooks.dart';
import 'package:mona/data/model/molecule.dart';
import 'package:mona/data/model/placement.dart';
@@ -44,6 +45,7 @@ class MedicationIntake with MedicationIntakeMappable {
@MappableField(hook: JsonStringHook())
final List placements;
final DosingBasis dosingBasis;
+ final InjectionType? injectionType;
MedicationIntake({
int? id,
@@ -62,6 +64,7 @@ class MedicationIntake with MedicationIntakeMappable {
this.notes,
this.placements = const [],
required this.dosingBasis,
+ this.injectionType,
}) : id = id ?? clock.now().millisecondsSinceEpoch {
if (takenDateTime != null && !takenDateTime!.isUtc) {
throw ArgumentError('takenDateTime must be UTC');
diff --git a/lib/data/model/medication_intake.mapper.dart b/lib/data/model/medication_intake.mapper.dart
index 116097286..2bf34fdf1 100644
--- a/lib/data/model/medication_intake.mapper.dart
+++ b/lib/data/model/medication_intake.mapper.dart
@@ -24,6 +24,7 @@ class MedicationIntakeMapper extends ClassMapperBase {
EsterMapper.ensureInitialized();
PlacementMapper.ensureInitialized();
DosingBasisMapper.ensureInitialized();
+ InjectionTypeMapper.ensureInitialized();
}
return _instance!;
}
@@ -131,6 +132,12 @@ class MedicationIntakeMapper extends ClassMapperBase {
'dosingBasis',
_$dosingBasis,
);
+ static InjectionType? _$injectionType(MedicationIntake v) => v.injectionType;
+ static const Field _f$injectionType = Field(
+ 'injectionType',
+ _$injectionType,
+ opt: true,
+ );
@override
final MappableFields fields = const {
@@ -150,6 +157,7 @@ class MedicationIntakeMapper extends ClassMapperBase {
#notes: _f$notes,
#placements: _f$placements,
#dosingBasis: _f$dosingBasis,
+ #injectionType: _f$injectionType,
};
static MedicationIntake _instantiate(DecodingData data) {
@@ -170,6 +178,7 @@ class MedicationIntakeMapper extends ClassMapperBase {
notes: data.dec(_f$notes),
placements: data.dec(_f$placements),
dosingBasis: data.dec(_f$dosingBasis),
+ injectionType: data.dec(_f$injectionType),
);
}
@@ -256,6 +265,7 @@ abstract class MedicationIntakeCopyWith<$R, $In extends MedicationIntake, $Out>
String? notes,
List? placements,
DosingBasis? dosingBasis,
+ InjectionType? injectionType,
});
MedicationIntakeCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t,
@@ -305,6 +315,7 @@ class _MedicationIntakeCopyWithImpl<$R, $Out>
Object? notes = $none,
List? placements,
DosingBasis? dosingBasis,
+ Object? injectionType = $none,
}) =>
$apply(
FieldCopyWithData({
@@ -327,6 +338,7 @@ class _MedicationIntakeCopyWithImpl<$R, $Out>
if (notes != $none) #notes: notes,
if (placements != null) #placements: placements,
if (dosingBasis != null) #dosingBasis: dosingBasis,
+ if (injectionType != $none) #injectionType: injectionType,
}),
);
@override
@@ -356,6 +368,7 @@ class _MedicationIntakeCopyWithImpl<$R, $Out>
notes: data.get(#notes, or: $value.notes),
placements: data.get(#placements, or: $value.placements),
dosingBasis: data.get(#dosingBasis, or: $value.dosingBasis),
+ injectionType: data.get(#injectionType, or: $value.injectionType),
);
@override
diff --git a/lib/i18n/en-GB.i18next.json b/lib/i18n/en-GB.i18next.json
index ae2d7b139..3687c76e6 100644
--- a/lib/i18n/en-GB.i18next.json
+++ b/lib/i18n/en-GB.i18next.json
@@ -74,7 +74,7 @@
"reportBugDescription": "Open an issue on GitHub",
"translateApp": "Translate the app",
"translateAppDescription": "Help translate Mona on Weblate",
- "languageMissing": "Is your language missing?",
+ "missingTranslation": "Is a translation missing?",
"donate": "Donate",
"donateDescription": "Support Mona on Ko-fi",
"backupSaved": "Backup saved",
@@ -159,6 +159,9 @@
"intakeRecorded": "Intake recorded",
"needleDeadSpace": "Needle dead space",
"notes": "Notes",
+ "injectionType": "Injection type",
+ "intramuscular": "Intramuscular",
+ "subcutaneous": "Subcutaneous",
"microliters": "μL",
"milliliters": "mL",
"empty_levels": "Add a blood test or log an estradiol injection to get started",
diff --git a/lib/i18n/en.i18next.json b/lib/i18n/en.i18next.json
index 9e0e3a06b..eacccbfff 100644
--- a/lib/i18n/en.i18next.json
+++ b/lib/i18n/en.i18next.json
@@ -74,7 +74,7 @@
"reportBugDescription": "Open an issue on GitHub",
"translateApp": "Translate the app",
"translateAppDescription": "Help translate Mona on Weblate",
- "languageMissing": "Is your language missing?",
+ "missingTranslation": "Is a translation missing?",
"donate": "Donate",
"donateDescription": "Support Mona on Ko-fi",
"backupSaved": "Backup saved",
@@ -159,6 +159,9 @@
"intakeRecorded": "Intake recorded",
"needleDeadSpace": "Needle dead space",
"notes": "Notes",
+ "injectionType": "Injection type",
+ "intramuscular": "Intramuscular",
+ "subcutaneous": "Subcutaneous",
"microliters": "μL",
"milliliters": "mL",
"empty_levels": "Add a blood test or log an estradiol injection to get started",
diff --git a/lib/i18n/fr.i18next.json b/lib/i18n/fr.i18next.json
index c57dacccc..428d29fc8 100644
--- a/lib/i18n/fr.i18next.json
+++ b/lib/i18n/fr.i18next.json
@@ -79,7 +79,7 @@
"reportBugDescription": "Ouvrir une issue sur GitHub",
"translateApp": "Traduire l'application",
"translateAppDescription": "Aider à traduire Mona sur Weblate",
- "languageMissing": "Il manque votre langue ?",
+ "missingTranslation": "Il manque une traduction ?",
"donate": "Faire un don",
"donateDescription": "Soutenir Mona sur Ko-fi",
"backupSaved": "Sauvegarde enregistrée",
@@ -169,6 +169,9 @@
"intakeRecorded": "Prise enregistrée",
"needleDeadSpace": "Espace mort de l'aiguille",
"notes": "Notes",
+ "injectionType": "Type d'injection",
+ "intramuscular": "Intramusculaire",
+ "subcutaneous": "Sous-cutanée",
"microliters": "μL",
"milliliters": "mL",
"empty_levels": "Ajoutez une prise de sang ou enregistrez une injection d'estradiol pour commencer",
diff --git a/lib/i18n/helpers/injection_type_l10n.dart b/lib/i18n/helpers/injection_type_l10n.dart
new file mode 100644
index 000000000..dbb445c53
--- /dev/null
+++ b/lib/i18n/helpers/injection_type_l10n.dart
@@ -0,0 +1,9 @@
+import 'package:mona/data/model/injection_type.dart';
+import 'package:mona/i18n/translations.g.dart';
+
+extension InjectionTypeL10n on InjectionType {
+ String get localizedName => switch (this) {
+ InjectionType.intramuscular => t.intramuscular,
+ InjectionType.subcutaneous => t.subcutaneous,
+ };
+}
diff --git a/lib/i18n/hi.i18next.json b/lib/i18n/hi.i18next.json
index e7e993e5f..d95ae3523 100644
--- a/lib/i18n/hi.i18next.json
+++ b/lib/i18n/hi.i18next.json
@@ -67,7 +67,7 @@
"autoUpdate": "Apne aap update",
"autoUpdateDescription": "Apne aap check jab naye updates launch ho jayenge",
"checkForUpdates": "Updates dekhe",
- "checkForUpdatesDescription": "Naye version ke liye manually check kare\\n yeh aapko internet se connect karega\\(Koi data nahi milega)",
+ "checkForUpdatesDescription": "Naye version ke liye manually check kare\\n yeh aapko internet se connect karega\\n(Koi data nahi milega)",
"appVersion": "मोना संस्करण {{version}}",
"backupSaved": "बैकअप बचाया",
"exportFailed": "निर्यात करने में विफल: {{error}}",
@@ -119,5 +119,199 @@
"onHrtForDays_one": "{{count}} दिनों के लिए HRT पर",
"onHrtForDays_other": "{{count}} दिनों के लिए HRT पर",
"onHrtForMonths_one": "{{count}} महीनों के लिए HRT पर",
- "onHrtForMonths_other": "{{count}} महीनों के लिए HRT पर"
-}
\ No newline at end of file
+ "onHrtForMonths_other": "{{count}} महीनों के लिए HRT पर",
+ "onHrtForWeeks_one": "HRT on {{count}} week",
+ "onHrtForWeeks_other": "HRT on {{count}} week",
+ "onHrtForYears_one": "HRT पर {{count}} वर्ष",
+ "onHrtForYears_other": "HRT पर {{count}} वर्ष",
+ "intakesLoggedCount_one": "{{count}} प्रवेश लॉग इन",
+ "intakesLoggedCount_other": "{{count}} प्रवेश लॉग इन",
+ "HrtCounter": "HRT पर समय",
+ "HrtCounterDescription": "जब तक आप एचआरटी और आपके कुल सेवन पर रहे हैं, तब तक दिखाएं",
+ "hrtWidgetPlaceholder": "अपना पहला सेवन लॉग इन करने के लिए मोना खोलें",
+ "hrtWidgetPreviewSample": "8 महीने के लिए HRT पर",
+ "hrtWidgetPreviewIntakeSample": "16 सेवन लॉग इन",
+ "startOfDayDescription": "समय पहले {{time}} पिछले दिन की ओर गिनती",
+ "chooseSchedule": "एक अनुसूची चुनें",
+ "addSchedulesFirst": "पहले शेड्यूल जोड़ें।।",
+ "editIntake": "सेवन संपादित करें",
+ "date": "तारीख",
+ "amount": "राशि",
+ "takenAmount": "ली गई राशि",
+ "wastedAmount": "अपशिष्ट राशि",
+ "none": "कोई नहीं",
+ "startOfDay": "दिन की शुरुआत",
+ "supplyItem": "आपूर्ति आइटम",
+ "chooseItem": "एक आइटम चुनें",
+ "noItemsToAdd": "उपलब्ध नहीं है",
+ "injectionSide": "इंजेक्शन साइड",
+ "deleteIntake": "इस सेवन को हटा दें?",
+ "takeMedication": "{{scheduleName}}",
+ "takeIntake": "सेवन करें",
+ "intakeRecorded": "दर्ज करना",
+ "needleDeadSpace": "सुई मृत अंतरिक्ष",
+ "notes": "नोट",
+ "microliters": "μL",
+ "milliliters": "एमएल",
+ "empty_levels": "एक रक्त परीक्षण जोड़ें या शुरू करने के लिए एक एस्ट्राडियोल इंजेक्शन लॉग इन करें",
+ "bloodTestsTitle": "रक्त परीक्षण",
+ "estradiolLevelsTitle": "एस्ट्राडियोल स्तर",
+ "week": "डब्ल्यू",
+ "twoWeeks": "2 डब्ल्यू",
+ "threeMonths": "3 एम",
+ "sixMonths": "6 एम",
+ "month": "एम",
+ "year": "वाई",
+ "empty_blood_tests": "रक्त परीक्षण यहाँ दिखाई देगा। ऐड बटन का उपयोग करके शुरू करें!",
+ "addBloodTest": "रक्त परीक्षण जोड़ें",
+ "editBloodTest": "रक्त परीक्षण",
+ "newBloodTest": "न्यू ब्लड टेस्ट",
+ "deleteBloodTest": "इस रक्त परीक्षण को हटा दें?",
+ "estradiolLevelLabel": "एस्ट्राडियोल स्तर",
+ "testosteroneLevelLabel": "टेस्टोस्टेरोन का स्तर",
+ "bloodTestDateLabel": "परीक्षा तिथि",
+ "chartNowConcentration": "अब {{value}}",
+ "chartBloodTestLevelTooltip": "{{date}}: {{level}}",
+ "chartLevelTooltip": "{{date}}: {{level}}",
+ "empty_supplies": "कोई आपूर्ति नहीं। शुरू करने के लिए एक आइटम जोड़ें।।",
+ "newItem": "नया आइटम",
+ "adminRoute": "प्रशासन मार्ग",
+ "totalAmount": "कुल राशि",
+ "concentration": "एकाग्रता",
+ "dosePerUnitLabel": "प्रति खुराक {{unit}}",
+ "editItem": "आइटम जोड़ें",
+ "usedAmount": "प्रयुक्त राशि",
+ "deleteItem": "{{name}}?",
+ "remaining_one": "{{count}} {{unit}} शेष",
+ "remaining_other": "{{count}} {{unit}} शेष",
+ "allItemsFilter": "सब",
+ "medicationItemsFilter": "दवा",
+ "genericItems": "उपभोग्य",
+ "medicationItemType": "दवा",
+ "genericItemType": "उपभोग्य",
+ "supplyType": "प्रकार",
+ "syringe": "सिरिंज",
+ "wipe": "वाइप्स",
+ "needle": "सुई",
+ "gloves": "दस्ताने",
+ "bandage": "बंधन",
+ "syringeRemaining_one": "{{count}} syringes शेष",
+ "syringeRemaining_other": "{{count}} syringes शेष",
+ "wipeRemaining_one": "{{count}} शेष पोंछे",
+ "wipeRemaining_other": "{{count}} शेष पोंछे",
+ "needleRemaining_one": "{{count}} सुई शेष",
+ "needleRemaining_other": "{{count}} सुई शेष",
+ "glovesRemaining_one": "{{count}} दस्ताने शेष",
+ "glovesRemaining_other": "{{count}} दस्ताने शेष",
+ "bandageRemaining_one": "{{count}} बैंडेज शेष",
+ "bandageRemaining_other": "{{count}} बैंडेज शेष",
+ "add": "जोड़ें",
+ "save": "सहेजें",
+ "cancel": "रद्द करना",
+ "next": "अगला",
+ "delete": "डिलीट",
+ "deleteElement": "इस मद को हटाएं?",
+ "irreversibleAction": "यह कार्रवाई नहीं हो सकती है।।",
+ "name": "नाम",
+ "molecule": "मोलेकुल",
+ "ester": "एस्टर",
+ "estradiol": "एस्ट्राडियोल",
+ "progesterone": "प्रोजेस्टेरोन",
+ "testosterone": "टेस्टोस्टेरोन",
+ "nandrolone": "नांद्र",
+ "dihydrotestosterone": "Dihydrotestosterone",
+ "spironolactone": "Spironolactone",
+ "cyproteroneAcetate": "Cyproterone एसीटेट",
+ "leuprorelinAcetate": "ल्यूप्रोरेलिन एसीटेट",
+ "bicalutamide": "Bicalutamide",
+ "decapeptyl": "Decapeptyl",
+ "raloxifene": "रालॉक्सीफेन",
+ "tamoxifen": "टैमोक्सीफेन",
+ "finasteride": "फिनस्टराइड",
+ "dutasteride": "Dutasteride",
+ "minoxidil": "मिनोक्सिडिल",
+ "pioglitazone": "Pioglitazone",
+ "enanthate": "Enanthate",
+ "valerate": "वैलेरेट",
+ "cypionate": "साइटमैप",
+ "undecylate": "Undecylate",
+ "benzoate": "बेंजोएट",
+ "cypionateSuspension": "Cypionate निलंबन",
+ "medicationEstradiolEnanthate": "Estradiol enanthate",
+ "medicationEstradiolValerate": "Estradiol valerate",
+ "medicationEstradiolCypionate": "Estradiol cypionate",
+ "medicationEstradiolUndecylate": "Estradiol undecylate",
+ "medicationEstradiolBenzoate": "Estradiol बेंजोएट",
+ "medicationEstradiolCypionateSuspension": "Estradiol cypionate निलंबन",
+ "medicationTestosteroneEnanthate": "टेस्टोस्टेरोन enanthate",
+ "medicationTestosteroneValerate": "टेस्टोस्टेरोन valerate",
+ "medicationTestosteroneCypionate": "टेस्टोस्टेरोन cypionate",
+ "medicationTestosteroneUndecylate": "टेस्टोस्टेरोन undecylate",
+ "medicationTestosteroneBenzoate": "टेस्टोस्टेरोन benzoate",
+ "medicationTestosteroneCypionateSuspension": "टेस्टोस्टेरोन साइपीओनेट निलंबन",
+ "injection": "इंजेक्शन",
+ "oral": "मौखिक",
+ "sublingual": "बहुभाषी",
+ "patch": "पैच",
+ "gel": "जेल",
+ "implant": "प्रत्यारोपण",
+ "suppository": "सपोसिटरी",
+ "transdermalSpray": "ट्रांसडर्मल स्प्रे",
+ "transdermalDrops": "ट्रांसडर्मल ड्रॉप",
+ "deliveryForm": "फॉर्म",
+ "deliveryFormPump": "पम्प",
+ "deliveryFormSachet": "साचेत",
+ "deliveryFormGram": "ट्यूब",
+ "unitMilligram": "मिलीग्राम",
+ "unitMicrogramPerDay": "μg / दिन",
+ "unitPgPerMl": "pg/ml",
+ "unitPmolPerL": "pmol",
+ "unitNgPerDl": "ng/dl",
+ "unitNmolPerL": "nmol/L",
+ "unitNgPerMl": "एनजी / एमएल",
+ "administrationRouteUnitMl_one": "एमएल",
+ "administrationRouteUnitMl_other": "एमएल",
+ "administrationRouteUnitPill_one": "गोलियाँ",
+ "administrationRouteUnitPill_other": "गोलियाँ",
+ "administrationRouteUnitPatch_one": "पैच",
+ "administrationRouteUnitPatch_other": "पैच",
+ "administrationRouteUnitPump_one": "पंप",
+ "administrationRouteUnitPump_other": "पंप",
+ "administrationRouteUnitSachet_one": "पाउच",
+ "administrationRouteUnitSachet_other": "पाउच",
+ "administrationRouteUnitGram_one": "ग्राम",
+ "administrationRouteUnitGram_other": "ग्राम",
+ "administrationRouteUnitImplant_one": "प्रत्यारोपण",
+ "administrationRouteUnitImplant_other": "प्रत्यारोपण",
+ "administrationRouteUnitSuppository_one": "सट्टा",
+ "administrationRouteUnitSuppository_other": "सट्टा",
+ "administrationRouteUnitSpray_one": "स्प्रे",
+ "administrationRouteUnitSpray_other": "स्प्रे",
+ "injectionSideLeft": "बाएं",
+ "injectionSideRight": "अधिकार",
+ "placementLeft": "बाएं तरफ",
+ "placementRight": "सही पक्ष",
+ "placementLeftThigh": "बाएं जांघ",
+ "placementRightThigh": "सही जांघ",
+ "placementLeftArm": "बाएं हाथ",
+ "placementRightArm": "दाहिने हाथ",
+ "placementLeftButtock": "बाएं बटॉक",
+ "placementRightButtock": "राइट बटॉक",
+ "placementLeftAbdomen": "बाएं पेट",
+ "placementRightAbdomen": "दाहिने पेट",
+ "applicationSites": "अनुप्रयोग साइट",
+ "applicationSitesDescription": "उन साइटों को प्रबंधित करें जो आप के बीच घूमते हैं",
+ "applicationSitesInstructions": "उन साइटों को प्रबंधित करें जो आप के बीच घूमते हैं। साइट्स को आपके सेवन इतिहास के आधार पर सुझाव दिया जाता है। फिर से व्यवस्थित करने के लिए लंबे प्रेस।।",
+ "addApplicationSite": "साइट जोड़ें",
+ "customSiteLabel": "कस्टम साइट नाम",
+ "noApplicationSitesYet": "अभी तक कोई साइट नहीं",
+ "addSiteToGetStarted": "शुरू करने के लिए नीचे एक साइट जोड़ें।।",
+ "placementSuggestionPerScheduleTitle": "प्रति अनुसूची सुझाव",
+ "placementSuggestionPerScheduleDescription": "इस कार्यक्रम के इतिहास पर अगले साइट सुझाव को बेस करें।।",
+ "requiredField": "आवश्यक फ़ील्ड",
+ "mustBePositiveNumber": "एक सकारात्मक संख्या होना चाहिए",
+ "mustBeBetween1And28": "1 और 28 के बीच होना चाहिए",
+ "mustBeAtMost": "{{max}}",
+ "invalidTotalAmount": "कुल राशि",
+ "cannotExceedTotalCapacity": "कुल क्षमता से अधिक नहीं"
+}
diff --git a/lib/i18n/hu.i18next.json b/lib/i18n/hu.i18next.json
new file mode 100644
index 000000000..6c5d56c68
--- /dev/null
+++ b/lib/i18n/hu.i18next.json
@@ -0,0 +1,87 @@
+{
+ "appTitle": "Mona",
+ "nav_home": "Mona",
+ "nav_intakes": "Bevevések",
+ "nav_levels": "Szintek",
+ "nav_supplies": "Készlet",
+ "takeAnIntake": "Bevevés felírása",
+ "addAnItem": "Elem hozzáadása",
+ "empty_home": "Kezdje egy időbeosztás hozzáadásával a Beállításokban",
+ "allDone": "Kész!",
+ "noIntakesDue": "Nincs mára felírt adag",
+ "upcoming": "Következő",
+ "asNeeded": "Ahogy szükséges",
+ "taken": "Bevéve",
+ "daysAgoCount_one": "{{count}} napja",
+ "daysAgoCount_other": "{{count}} napja",
+ "yesterday": "tegnap",
+ "inDaysCount_one": "{{count}} nap múlva",
+ "inDaysCount_other": "{{count}} nap múlva",
+ "tomorrow": "holnap",
+ "lastTaken": "Utolsó bevevés",
+ "neverTakenYet": "Nincs bevevés",
+ "scheduleFrequencyDaily": "Naponta",
+ "scheduleFrequencyDailyDescription": "Minden nap, specifikus időben",
+ "scheduleFrequencyEveryNDays_one": "Minden nap",
+ "scheduleFrequencyEveryNDays_other": "{{count}} naponta",
+ "scheduleFrequencyInterval": "Intervallum",
+ "scheduleFrequencyIntervalDescription": "Néhány naponta",
+ "scheduleFrequencyWeekly": "Hetente",
+ "scheduleFrequencyMonthly": "Havonta",
+ "scheduleFrequencyMonthlyDescription": "Minden hónap ugyanazon napján",
+ "scheduleFrequencyOnDayEveryNMonths_one": "Minden hónap {{day}}.-je",
+ "scheduleFrequencyOnDayEveryNMonths_other": "{{day}}.-án/én, minden {{count}}. -dik hónapban",
+ "scheduleFrequencyAsNeeded": "Szükség szerint",
+ "scheduleFrequencyAsNeededDescription": "Nincs rögzített ütemezés",
+ "newUpdateAvailable": "Új frissítés érhető el!",
+ "settingsTitle": "Beállítások",
+ "notifications": "Értesítések",
+ "schedulesAndNotifications": "Ütemezések és értesítések",
+ "general": "Általános",
+ "schedules": "Ütemezések",
+ "noSchedules": "Nincsen ütemezés",
+ "schedulesCreated_one": "{{count}} létrehozva",
+ "schedulesCreated_other": "{{count}} létrehozva",
+ "language": "Nyelv",
+ "languageFollowDevice": "Használja az eszköz nyelvét",
+ "enableNotifications": "Érdesítések bekapcsolása",
+ "enableNotificationsDescription": "Küldj emlékeztetőket",
+ "anchorToLastIntake": "Újraszámítás a legútóbbi bevitel alapján",
+ "scheduleFrequencyWeeklyDescription": "A hét egy napján",
+ "goToSettings": "Menj a beállításokhoz",
+ "anchorToLastIntakeDescription": "Ütemezi a következő bevitelt egy teljes intervallum után a legutóbbi bevitel óta",
+ "notificationsDisabledTitle": "Az értesítések ki vannak kapcsolva",
+ "clickToOpenSettings": "Kattintson beállítások megbyitásához",
+ "exactRemindersDisabled": "A pontos emlékeztetők ki vannak kapcsolva",
+ "remindersDelayed": "Az emlékeztetők kissé késhetnek. Kattintson beállítások megnyitásahoz.",
+ "medicalSettings": "Egészségügyi beállítások",
+ "theme": "Téma",
+ "themeCustomizeColors": "App színek testreszabása",
+ "customThemeEnabled": "Saját téma",
+ "themeGenerate": "Frissítés",
+ "importFailed": "Importálás sikertelen: {{error}}",
+ "updates": "Frissítések",
+ "dataManagement": "Adat Kezelés",
+ "exportDataTitle": "Adatok Exportálása",
+ "exportDataSubtitle": "Adatok mentése JSON fájlba",
+ "units": "Mértékegységek",
+ "updateNoCompatibleApk": "Az ezközöddel kompatibilis frissítés nem található.",
+ "updateAppUpToDate": "Az app naprakész!",
+ "updateCheckNetworkError": "A frissítés ellenőrzése sikertelen.",
+ "updateDialogTitle": "Új frissítés érhető el",
+ "updateDialogBody": "A(z) {{latest}} verzió elérhetőve vált! (Jelenlegi: {{current}})\\n\\nEgy ezközöddel kompartibilis frissítés készen áll a telepítésre.",
+ "updateDownloadAndInstall": "Letöltés és Telepítés",
+ "updateInstallPermissionRequired": "Engedély szükséges a frissítés telepítéséhez.",
+ "updateDownloadingTitle": "Frissítés Letöltése...",
+ "updateFailedOpenInstaller": "A telepítő megnyitása sikertelen volt: {{message}}",
+ "updateDownloadFailed": "Letöltés sikertelen. Kérem ellenőrizze a kapcsolatát.",
+ "secretSettings": "Titkos beállítások",
+ "slimeMode": "Slime mód",
+ "themeVariant": "Variáns",
+ "themeContrast": "Kontraszt",
+ "themeContrastStandard": "Átlagos",
+ "themeContrastMedium": "Közepes",
+ "themeContrastHigh": "Magas",
+ "autoUpdate": "Auto-Frissítés",
+ "autoUpdateDescription": "Frissítes autómatikus ellenőrzése az app megnyitásakor"
+}
diff --git a/lib/i18n/id.i18next.json b/lib/i18n/id.i18next.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/lib/i18n/id.i18next.json
@@ -0,0 +1 @@
+{}
diff --git a/lib/i18n/pt-BR.i18next.json b/lib/i18n/pt-BR.i18next.json
index b8dfae0c7..f4254b816 100644
--- a/lib/i18n/pt-BR.i18next.json
+++ b/lib/i18n/pt-BR.i18next.json
@@ -11,18 +11,18 @@
"noIntakesDue": "Nenhuma dose pendente hoje",
"upcoming": "Próximos",
"taken": "Tomado",
- "daysAgoCount_one": "há {{count}} dias",
+ "daysAgoCount_one": "há {{count}} dia",
"daysAgoCount_many": "há {{count}} dias",
"daysAgoCount_other": "há {{count}} dias",
"yesterday": "ontem",
- "inDaysCount_one": "em {{count}} dias",
+ "inDaysCount_one": "em {{count}} dia",
"inDaysCount_many": "em {{count}} dias",
"inDaysCount_other": "em {{count}} dias",
"tomorrow": "amanhã",
"lastTaken": "Última dose",
"neverTakenYet": "Ainda não tomado",
- "scheduleFrequencyDaily": "Todos os dias",
- "scheduleFrequencyEveryNDays_one": "A cada {{count}} dias",
+ "scheduleFrequencyDaily": "Diariamente",
+ "scheduleFrequencyEveryNDays_one": "A cada {{count}} dia",
"scheduleFrequencyEveryNDays_many": "A cada {{count}} dias",
"scheduleFrequencyEveryNDays_other": "A cada {{count}} dias",
"scheduleFrequencyInterval": "Intervalo",
@@ -243,7 +243,7 @@
"invalidTotalAmount": "Quantidade total inválida",
"cannotExceedTotalCapacity": "Não pode exceder a capacidade total",
"scheduleFrequencyMonthly": "Mensal",
- "scheduleFrequencyOnDayEveryNMonths_one": "Dia {{day}}, a cada {{count}} mês",
+ "scheduleFrequencyOnDayEveryNMonths_one": "Dia {{day}}, a cada mês",
"scheduleFrequencyOnDayEveryNMonths_many": "Dia {{day}}, a cada {{count}} meses",
"scheduleFrequencyOnDayEveryNMonths_other": "Dia {{day}}, a cada {{count}} meses",
"anchorToLastIntake": "Recalcular com base na última dose",
@@ -305,5 +305,48 @@
"addSiteToGetStarted": "Adicione um local abaixo para começar.",
"placementSuggestionPerScheduleTitle": "Sugerir por agendamento",
"placementSuggestionPerScheduleDescription": "Baseie a sugestão do próximo local apenas no histórico deste agendamento.",
- "mustBeBetween1And28": "Deve estar entre 1 e 28"
-}
\ No newline at end of file
+ "mustBeBetween1And28": "Deve estar entre 1 e 28",
+ "asNeeded": "Conforme necessário",
+ "scheduleFrequencyDailyDescription": "Todos os dias, em horários específicos",
+ "scheduleFrequencyIntervalDescription": "A cada poucos dias",
+ "scheduleFrequencyWeeklyDescription": "Alguns dias da semana",
+ "scheduleFrequencyMonthlyDescription": "Mesmo dia a cada mês",
+ "scheduleFrequencyAsNeeded": "Quando necessário",
+ "scheduleFrequencyAsNeededDescription": "Sem horário pré‐estabelecido",
+ "backupSaved": "Dados salvo",
+ "reportBug": "Reportar um bug",
+ "translateAppDescription": "Ajude a traduzir o mona no Weblate",
+ "donate": "Faça uma doação",
+ "translateApp": "Traduzir o aplicativo",
+ "getInvolved": "Contribua",
+ "reportBugDescription": "Abra uma issue no Github",
+ "donateDescription": "Apoie Mona no Ko‐fi",
+ "missingTranslation": "Tradução faltando?",
+ "slimeMode": "Modo Slime",
+ "secretSettings": "Configurações ocultas",
+ "applicationSites": "Locais de aplicação",
+ "applicationSitesInstructions": "Gerencie os locais de aplicação alternando entre eles. Os locais são sugeridos com base em seu histórico de doses. Pressione e segure para reordenar.",
+ "mustBeAtMost": "No máximo {{max}}",
+ "month": "M",
+ "year": "A",
+ "estradiolLevelsTitle": "Níveis de Estradiol",
+ "week": "S",
+ "twoWeeks": "2 S",
+ "threeMonths": "3 M",
+ "sixMonths": "6 M",
+ "empty_levels": "Adicione um exame de sangue ou registre uma injeção de estradiol para começar",
+ "deliveryFormPump": "Pump",
+ "deliveryFormSachet": "Sachê",
+ "unitMicrogramPerDay": "µg/dia",
+ "unitNgPerMl": "ng/mL",
+ "deliveryForm": "Forma",
+ "administrationRouteUnitSachet_one": "sachê",
+ "administrationRouteUnitSachet_many": "sachês",
+ "administrationRouteUnitSachet_other": "sachês",
+ "administrationRouteUnitGram_one": "grama",
+ "administrationRouteUnitGram_many": "gramas",
+ "administrationRouteUnitGram_other": "gramas",
+ "injectionType": "Tipo de injeção",
+ "intramuscular": "Intramuscular",
+ "subcutaneous": "Subcutânea"
+}
diff --git a/lib/i18n/translations.g.dart b/lib/i18n/translations.g.dart
index 524af0e8d..d5a89daec 100644
--- a/lib/i18n/translations.g.dart
+++ b/lib/i18n/translations.g.dart
@@ -3,8 +3,8 @@
/// Source: lib/i18n/generated
/// To regenerate, run: `dart run slang`
///
-/// Locales: 24
-/// Strings: 5909 (246 per locale)
+/// Locales: 26
+/// Strings: 6199 (238 per locale)
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
@@ -21,6 +21,8 @@ import 'translations_et.g.dart' as l_et;
import 'translations_fr.g.dart' as l_fr;
import 'translations_gl.g.dart' as l_gl;
import 'translations_hi.g.dart' as l_hi;
+import 'translations_hu.g.dart' as l_hu;
+import 'translations_id.g.dart' as l_id;
import 'translations_is.g.dart' as l_is;
import 'translations_it.g.dart' as l_it;
import 'translations_ko.g.dart' as l_ko;
@@ -54,6 +56,8 @@ enum AppLocale with BaseAppLocale {
fr(languageCode: 'fr'),
gl(languageCode: 'gl'),
hi(languageCode: 'hi'),
+ hu(languageCode: 'hu'),
+ id(languageCode: 'id'),
icelandic(languageCode: 'is'),
it(languageCode: 'it'),
ko(languageCode: 'ko'),
@@ -147,6 +151,18 @@ enum AppLocale with BaseAppLocale {
cardinalResolver: cardinalResolver,
ordinalResolver: ordinalResolver,
);
+ case AppLocale.hu:
+ return l_hu.TranslationsHu(
+ overrides: overrides,
+ cardinalResolver: cardinalResolver,
+ ordinalResolver: ordinalResolver,
+ );
+ case AppLocale.id:
+ return l_id.TranslationsId(
+ overrides: overrides,
+ cardinalResolver: cardinalResolver,
+ ordinalResolver: ordinalResolver,
+ );
case AppLocale.icelandic:
return l_is.TranslationsIs(
overrides: overrides,
diff --git a/lib/i18n/translations_en.g.dart b/lib/i18n/translations_en.g.dart
index 02e97a067..109f874c8 100644
--- a/lib/i18n/translations_en.g.dart
+++ b/lib/i18n/translations_en.g.dart
@@ -251,8 +251,8 @@ class Translations with BaseTranslations {
/// en: 'Help translate Mona on Weblate'
String get translateAppDescription => 'Help translate Mona on Weblate';
- /// en: 'Is your language missing?'
- String get languageMissing => 'Is your language missing?';
+ /// en: 'Is a translation missing?'
+ String get missingTranslation => 'Is a translation missing?';
/// en: 'Donate'
String get donate => 'Donate';
@@ -491,6 +491,15 @@ class Translations with BaseTranslations {
/// en: 'Notes'
String get notes => 'Notes';
+ /// en: 'Injection type'
+ String get injectionType => 'Injection type';
+
+ /// en: 'Intramuscular'
+ String get intramuscular => 'Intramuscular';
+
+ /// en: 'Subcutaneous'
+ String get subcutaneous => 'Subcutaneous';
+
/// en: 'μL'
String get microliters => 'μL';
@@ -1179,7 +1188,7 @@ extension on Translations {
'reportBugDescription' => 'Open an issue on GitHub',
'translateApp' => 'Translate the app',
'translateAppDescription' => 'Help translate Mona on Weblate',
- 'languageMissing' => 'Is your language missing?',
+ 'missingTranslation' => 'Is a translation missing?',
'donate' => 'Donate',
'donateDescription' => 'Support Mona on Ko-fi',
'backupSaved' => 'Backup saved',
@@ -1270,6 +1279,9 @@ extension on Translations {
'intakeRecorded' => 'Intake recorded',
'needleDeadSpace' => 'Needle dead space',
'notes' => 'Notes',
+ 'injectionType' => 'Injection type',
+ 'intramuscular' => 'Intramuscular',
+ 'subcutaneous' => 'Subcutaneous',
'microliters' => 'μL',
'milliliters' => 'mL',
'empty_levels' =>
diff --git a/lib/i18n/translations_en_GB.g.dart b/lib/i18n/translations_en_GB.g.dart
index 19f95f828..9614c3dad 100644
--- a/lib/i18n/translations_en_GB.g.dart
+++ b/lib/i18n/translations_en_GB.g.dart
@@ -189,7 +189,7 @@ class TranslationsEnGb extends Translations
@override
String get translateAppDescription => 'Help translate Mona on Weblate';
@override
- String get languageMissing => 'Is your language missing?';
+ String get missingTranslation => 'Is a translation missing?';
@override
String get donate => 'Donate';
@override
@@ -354,6 +354,12 @@ class TranslationsEnGb extends Translations
@override
String get notes => 'Notes';
@override
+ String get injectionType => 'Injection type';
+ @override
+ String get intramuscular => 'Intramuscular';
+ @override
+ String get subcutaneous => 'Subcutaneous';
+ @override
String get microliters => 'μL';
@override
String get milliliters => 'mL';
@@ -884,7 +890,7 @@ extension on TranslationsEnGb {
'reportBugDescription' => 'Open an issue on GitHub',
'translateApp' => 'Translate the app',
'translateAppDescription' => 'Help translate Mona on Weblate',
- 'languageMissing' => 'Is your language missing?',
+ 'missingTranslation' => 'Is a translation missing?',
'donate' => 'Donate',
'donateDescription' => 'Support Mona on Ko-fi',
'backupSaved' => 'Backup saved',
@@ -975,6 +981,9 @@ extension on TranslationsEnGb {
'intakeRecorded' => 'Intake recorded',
'needleDeadSpace' => 'Needle dead space',
'notes' => 'Notes',
+ 'injectionType' => 'Injection type',
+ 'intramuscular' => 'Intramuscular',
+ 'subcutaneous' => 'Subcutaneous',
'microliters' => 'μL',
'milliliters' => 'mL',
'empty_levels' =>
diff --git a/lib/i18n/translations_fr.g.dart b/lib/i18n/translations_fr.g.dart
index ea972c141..9a0b74504 100644
--- a/lib/i18n/translations_fr.g.dart
+++ b/lib/i18n/translations_fr.g.dart
@@ -193,7 +193,7 @@ class TranslationsFr extends Translations
@override
String get translateAppDescription => 'Aider à traduire Mona sur Weblate';
@override
- String get languageMissing => 'Il manque votre langue ?';
+ String get missingTranslation => 'Il manque une traduction ?';
@override
String get donate => 'Faire un don';
@override
@@ -363,6 +363,12 @@ class TranslationsFr extends Translations
@override
String get notes => 'Notes';
@override
+ String get injectionType => 'Type d\'injection';
+ @override
+ String get intramuscular => 'Intramusculaire';
+ @override
+ String get subcutaneous => 'Sous-cutanée';
+ @override
String get microliters => 'μL';
@override
String get milliliters => 'mL';
@@ -923,7 +929,7 @@ extension on TranslationsFr {
'reportBugDescription' => 'Ouvrir une issue sur GitHub',
'translateApp' => 'Traduire l\'application',
'translateAppDescription' => 'Aider à traduire Mona sur Weblate',
- 'languageMissing' => 'Il manque votre langue ?',
+ 'missingTranslation' => 'Il manque une traduction ?',
'donate' => 'Faire un don',
'donateDescription' => 'Soutenir Mona sur Ko-fi',
'backupSaved' => 'Sauvegarde enregistrée',
@@ -1019,6 +1025,9 @@ extension on TranslationsFr {
'intakeRecorded' => 'Prise enregistrée',
'needleDeadSpace' => 'Espace mort de l\'aiguille',
'notes' => 'Notes',
+ 'injectionType' => 'Type d\'injection',
+ 'intramuscular' => 'Intramusculaire',
+ 'subcutaneous' => 'Sous-cutanée',
'microliters' => 'μL',
'milliliters' => 'mL',
'empty_levels' =>
diff --git a/lib/i18n/translations_hi.g.dart b/lib/i18n/translations_hi.g.dart
index 3875c5bbd..abf87aa8f 100644
--- a/lib/i18n/translations_hi.g.dart
+++ b/lib/i18n/translations_hi.g.dart
@@ -174,7 +174,7 @@ class TranslationsHi extends Translations
String get checkForUpdates => 'Updates dekhe';
@override
String get checkForUpdatesDescription =>
- 'Naye version ke liye manually check kare\n yeh aapko internet se connect karega\(Koi data nahi milega)';
+ 'Naye version ke liye manually check kare\n yeh aapko internet se connect karega\n(Koi data nahi milega)';
@override
String appVersion({required Object version}) => 'मोना संस्करण ${version}';
@override
@@ -285,6 +285,336 @@ class TranslationsHi extends Translations
@override
String get empty_intakes => 'ले लिया गया सेवन यहाँ दिखाई देगा';
@override
+ String get HrtCounter => 'HRT पर समय';
+ @override
+ String get HrtCounterDescription =>
+ 'जब तक आप एचआरटी और आपके कुल सेवन पर रहे हैं, तब तक दिखाएं';
+ @override
+ String get hrtWidgetPlaceholder =>
+ 'अपना पहला सेवन लॉग इन करने के लिए मोना खोलें';
+ @override
+ String get hrtWidgetPreviewSample => '8 महीने के लिए HRT पर';
+ @override
+ String get hrtWidgetPreviewIntakeSample => '16 सेवन लॉग इन';
+ @override
+ String startOfDayDescription({required Object time}) =>
+ 'समय पहले ${time} पिछले दिन की ओर गिनती';
+ @override
+ String get chooseSchedule => 'एक अनुसूची चुनें';
+ @override
+ String get addSchedulesFirst => 'पहले शेड्यूल जोड़ें।।';
+ @override
+ String get editIntake => 'सेवन संपादित करें';
+ @override
+ String get date => 'तारीख';
+ @override
+ String get amount => 'राशि';
+ @override
+ String get takenAmount => 'ली गई राशि';
+ @override
+ String get wastedAmount => 'अपशिष्ट राशि';
+ @override
+ String get none => 'कोई नहीं';
+ @override
+ String get startOfDay => 'दिन की शुरुआत';
+ @override
+ String get supplyItem => 'आपूर्ति आइटम';
+ @override
+ String get chooseItem => 'एक आइटम चुनें';
+ @override
+ String get noItemsToAdd => 'उपलब्ध नहीं है';
+ @override
+ String get injectionSide => 'इंजेक्शन साइड';
+ @override
+ String get deleteIntake => 'इस सेवन को हटा दें?';
+ @override
+ String takeMedication({required Object scheduleName}) => '${scheduleName}';
+ @override
+ String get takeIntake => 'सेवन करें';
+ @override
+ String get intakeRecorded => 'दर्ज करना';
+ @override
+ String get needleDeadSpace => 'सुई मृत अंतरिक्ष';
+ @override
+ String get notes => 'नोट';
+ @override
+ String get microliters => 'μL';
+ @override
+ String get milliliters => 'एमएल';
+ @override
+ String get empty_levels =>
+ 'एक रक्त परीक्षण जोड़ें या शुरू करने के लिए एक एस्ट्राडियोल इंजेक्शन लॉग इन करें';
+ @override
+ String get bloodTestsTitle => 'रक्त परीक्षण';
+ @override
+ String get estradiolLevelsTitle => 'एस्ट्राडियोल स्तर';
+ @override
+ String get week => 'डब्ल्यू';
+ @override
+ String get twoWeeks => '2 डब्ल्यू';
+ @override
+ String get threeMonths => '3 एम';
+ @override
+ String get sixMonths => '6 एम';
+ @override
+ String get month => 'एम';
+ @override
+ String get year => 'वाई';
+ @override
+ String get empty_blood_tests =>
+ 'रक्त परीक्षण यहाँ दिखाई देगा। ऐड बटन का उपयोग करके शुरू करें!';
+ @override
+ String get addBloodTest => 'रक्त परीक्षण जोड़ें';
+ @override
+ String get editBloodTest => 'रक्त परीक्षण';
+ @override
+ String get newBloodTest => 'न्यू ब्लड टेस्ट';
+ @override
+ String get deleteBloodTest => 'इस रक्त परीक्षण को हटा दें?';
+ @override
+ String get estradiolLevelLabel => 'एस्ट्राडियोल स्तर';
+ @override
+ String get testosteroneLevelLabel => 'टेस्टोस्टेरोन का स्तर';
+ @override
+ String get bloodTestDateLabel => 'परीक्षा तिथि';
+ @override
+ String chartNowConcentration({required Object value}) => 'अब ${value}';
+ @override
+ String chartBloodTestLevelTooltip(
+ {required Object date, required Object level}) =>
+ '${date}: ${level}';
+ @override
+ String chartLevelTooltip({required Object date, required Object level}) =>
+ '${date}: ${level}';
+ @override
+ String get empty_supplies =>
+ 'कोई आपूर्ति नहीं। शुरू करने के लिए एक आइटम जोड़ें।।';
+ @override
+ String get newItem => 'नया आइटम';
+ @override
+ String get adminRoute => 'प्रशासन मार्ग';
+ @override
+ String get totalAmount => 'कुल राशि';
+ @override
+ String get concentration => 'एकाग्रता';
+ @override
+ String dosePerUnitLabel({required Object unit}) => 'प्रति खुराक ${unit}';
+ @override
+ String get editItem => 'आइटम जोड़ें';
+ @override
+ String get usedAmount => 'प्रयुक्त राशि';
+ @override
+ String deleteItem({required Object name}) => '${name}?';
+ @override
+ String get allItemsFilter => 'सब';
+ @override
+ String get medicationItemsFilter => 'दवा';
+ @override
+ String get genericItems => 'उपभोग्य';
+ @override
+ String get medicationItemType => 'दवा';
+ @override
+ String get genericItemType => 'उपभोग्य';
+ @override
+ String get supplyType => 'प्रकार';
+ @override
+ String get syringe => 'सिरिंज';
+ @override
+ String get wipe => 'वाइप्स';
+ @override
+ String get needle => 'सुई';
+ @override
+ String get gloves => 'दस्ताने';
+ @override
+ String get bandage => 'बंधन';
+ @override
+ String get add => 'जोड़ें';
+ @override
+ String get save => 'सहेजें';
+ @override
+ String get cancel => 'रद्द करना';
+ @override
+ String get next => 'अगला';
+ @override
+ String get delete => 'डिलीट';
+ @override
+ String get deleteElement => 'इस मद को हटाएं?';
+ @override
+ String get irreversibleAction => 'यह कार्रवाई नहीं हो सकती है।।';
+ @override
+ String get name => 'नाम';
+ @override
+ String get molecule => 'मोलेकुल';
+ @override
+ String get ester => 'एस्टर';
+ @override
+ String get estradiol => 'एस्ट्राडियोल';
+ @override
+ String get progesterone => 'प्रोजेस्टेरोन';
+ @override
+ String get testosterone => 'टेस्टोस्टेरोन';
+ @override
+ String get nandrolone => 'नांद्र';
+ @override
+ String get dihydrotestosterone => 'Dihydrotestosterone';
+ @override
+ String get spironolactone => 'Spironolactone';
+ @override
+ String get cyproteroneAcetate => 'Cyproterone एसीटेट';
+ @override
+ String get leuprorelinAcetate => 'ल्यूप्रोरेलिन एसीटेट';
+ @override
+ String get bicalutamide => 'Bicalutamide';
+ @override
+ String get decapeptyl => 'Decapeptyl';
+ @override
+ String get raloxifene => 'रालॉक्सीफेन';
+ @override
+ String get tamoxifen => 'टैमोक्सीफेन';
+ @override
+ String get finasteride => 'फिनस्टराइड';
+ @override
+ String get dutasteride => 'Dutasteride';
+ @override
+ String get minoxidil => 'मिनोक्सिडिल';
+ @override
+ String get pioglitazone => 'Pioglitazone';
+ @override
+ String get enanthate => 'Enanthate';
+ @override
+ String get valerate => 'वैलेरेट';
+ @override
+ String get cypionate => 'साइटमैप';
+ @override
+ String get undecylate => 'Undecylate';
+ @override
+ String get benzoate => 'बेंजोएट';
+ @override
+ String get cypionateSuspension => 'Cypionate निलंबन';
+ @override
+ String get medicationEstradiolEnanthate => 'Estradiol enanthate';
+ @override
+ String get medicationEstradiolValerate => 'Estradiol valerate';
+ @override
+ String get medicationEstradiolCypionate => 'Estradiol cypionate';
+ @override
+ String get medicationEstradiolUndecylate => 'Estradiol undecylate';
+ @override
+ String get medicationEstradiolBenzoate => 'Estradiol बेंजोएट';
+ @override
+ String get medicationEstradiolCypionateSuspension =>
+ 'Estradiol cypionate निलंबन';
+ @override
+ String get medicationTestosteroneEnanthate => 'टेस्टोस्टेरोन enanthate';
+ @override
+ String get medicationTestosteroneValerate => 'टेस्टोस्टेरोन valerate';
+ @override
+ String get medicationTestosteroneCypionate => 'टेस्टोस्टेरोन cypionate';
+ @override
+ String get medicationTestosteroneUndecylate => 'टेस्टोस्टेरोन undecylate';
+ @override
+ String get medicationTestosteroneBenzoate => 'टेस्टोस्टेरोन benzoate';
+ @override
+ String get medicationTestosteroneCypionateSuspension =>
+ 'टेस्टोस्टेरोन साइपीओनेट निलंबन';
+ @override
+ String get injection => 'इंजेक्शन';
+ @override
+ String get oral => 'मौखिक';
+ @override
+ String get sublingual => 'बहुभाषी';
+ @override
+ String get patch => 'पैच';
+ @override
+ String get gel => 'जेल';
+ @override
+ String get implant => 'प्रत्यारोपण';
+ @override
+ String get suppository => 'सपोसिटरी';
+ @override
+ String get transdermalSpray => 'ट्रांसडर्मल स्प्रे';
+ @override
+ String get transdermalDrops => 'ट्रांसडर्मल ड्रॉप';
+ @override
+ String get deliveryForm => 'फॉर्म';
+ @override
+ String get deliveryFormPump => 'पम्प';
+ @override
+ String get deliveryFormSachet => 'साचेत';
+ @override
+ String get deliveryFormGram => 'ट्यूब';
+ @override
+ String get unitMilligram => 'मिलीग्राम';
+ @override
+ String get unitMicrogramPerDay => 'μg / दिन';
+ @override
+ String get unitPgPerMl => 'pg/ml';
+ @override
+ String get unitPmolPerL => 'pmol';
+ @override
+ String get unitNgPerDl => 'ng/dl';
+ @override
+ String get unitNmolPerL => 'nmol/L';
+ @override
+ String get unitNgPerMl => 'एनजी / एमएल';
+ @override
+ String get injectionSideLeft => 'बाएं';
+ @override
+ String get injectionSideRight => 'अधिकार';
+ @override
+ String get placementLeft => 'बाएं तरफ';
+ @override
+ String get placementRight => 'सही पक्ष';
+ @override
+ String get placementLeftThigh => 'बाएं जांघ';
+ @override
+ String get placementRightThigh => 'सही जांघ';
+ @override
+ String get placementLeftArm => 'बाएं हाथ';
+ @override
+ String get placementRightArm => 'दाहिने हाथ';
+ @override
+ String get placementLeftButtock => 'बाएं बटॉक';
+ @override
+ String get placementRightButtock => 'राइट बटॉक';
+ @override
+ String get placementLeftAbdomen => 'बाएं पेट';
+ @override
+ String get placementRightAbdomen => 'दाहिने पेट';
+ @override
+ String get applicationSites => 'अनुप्रयोग साइट';
+ @override
+ String get applicationSitesDescription =>
+ 'उन साइटों को प्रबंधित करें जो आप के बीच घूमते हैं';
+ @override
+ String get applicationSitesInstructions =>
+ 'उन साइटों को प्रबंधित करें जो आप के बीच घूमते हैं। साइट्स को आपके सेवन इतिहास के आधार पर सुझाव दिया जाता है। फिर से व्यवस्थित करने के लिए लंबे प्रेस।।';
+ @override
+ String get addApplicationSite => 'साइट जोड़ें';
+ @override
+ String get customSiteLabel => 'कस्टम साइट नाम';
+ @override
+ String get noApplicationSitesYet => 'अभी तक कोई साइट नहीं';
+ @override
+ String get addSiteToGetStarted => 'शुरू करने के लिए नीचे एक साइट जोड़ें।।';
+ @override
+ String get placementSuggestionPerScheduleTitle => 'प्रति अनुसूची सुझाव';
+ @override
+ String get placementSuggestionPerScheduleDescription =>
+ 'इस कार्यक्रम के इतिहास पर अगले साइट सुझाव को बेस करें।।';
+ @override
+ String get requiredField => 'आवश्यक फ़ील्ड';
+ @override
+ String get mustBePositiveNumber => 'एक सकारात्मक संख्या होना चाहिए';
+ @override
+ String get mustBeBetween1And28 => '1 और 28 के बीच होना चाहिए';
+ @override
+ String mustBeAtMost({required Object max}) => '${max}';
+ @override
+ String get invalidTotalAmount => 'कुल राशि';
+ @override
+ String get cannotExceedTotalCapacity => 'कुल क्षमता से अधिक नहीं';
+ @override
String daysAgoCount({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
count,
@@ -334,6 +664,132 @@ class TranslationsHi extends Translations
one: '${count} महीनों के लिए HRT पर',
other: '${count} महीनों के लिए HRT पर',
);
+ @override
+ String onHrtForWeeks({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'HRT on ${count} week',
+ other: 'HRT on ${count} week',
+ );
+ @override
+ String onHrtForYears({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'HRT पर ${count} वर्ष',
+ other: 'HRT पर ${count} वर्ष',
+ );
+ @override
+ String intakesLoggedCount({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} प्रवेश लॉग इन',
+ other: '${count} प्रवेश लॉग इन',
+ );
+ @override
+ String remaining({required num count, required Object unit}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} ${unit} शेष',
+ other: '${count} ${unit} शेष',
+ );
+ @override
+ String syringeRemaining({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} syringes शेष',
+ other: '${count} syringes शेष',
+ );
+ @override
+ String wipeRemaining({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} शेष पोंछे',
+ other: '${count} शेष पोंछे',
+ );
+ @override
+ String needleRemaining({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} सुई शेष',
+ other: '${count} सुई शेष',
+ );
+ @override
+ String glovesRemaining({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} दस्ताने शेष',
+ other: '${count} दस्ताने शेष',
+ );
+ @override
+ String bandageRemaining({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} बैंडेज शेष',
+ other: '${count} बैंडेज शेष',
+ );
+ @override
+ String administrationRouteUnitMl({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'एमएल',
+ other: 'एमएल',
+ );
+ @override
+ String administrationRouteUnitPill({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'गोलियाँ',
+ other: 'गोलियाँ',
+ );
+ @override
+ String administrationRouteUnitPatch({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'पैच',
+ other: 'पैच',
+ );
+ @override
+ String administrationRouteUnitPump({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'पंप',
+ other: 'पंप',
+ );
+ @override
+ String administrationRouteUnitSachet({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'पाउच',
+ other: 'पाउच',
+ );
+ @override
+ String administrationRouteUnitGram({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'ग्राम',
+ other: 'ग्राम',
+ );
+ @override
+ String administrationRouteUnitImplant({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'प्रत्यारोपण',
+ other: 'प्रत्यारोपण',
+ );
+ @override
+ String administrationRouteUnitSuppository({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'सट्टा',
+ other: 'सट्टा',
+ );
+ @override
+ String administrationRouteUnitSpray({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'स्प्रे',
+ other: 'स्प्रे',
+ );
}
/// The flat map containing all translations for locale .
@@ -406,7 +862,7 @@ extension on TranslationsHi {
'Apne aap check jab naye updates launch ho jayenge',
'checkForUpdates' => 'Updates dekhe',
'checkForUpdatesDescription' =>
- 'Naye version ke liye manually check kare\n yeh aapko internet se connect karega\(Koi data nahi milega)',
+ 'Naye version ke liye manually check kare\n yeh aapko internet se connect karega\n(Koi data nahi milega)',
'appVersion' => ({required Object version}) => 'मोना संस्करण ${version}',
'backupSaved' => 'बैकअप बचाया',
'exportFailed' => ({required Object error}) =>
@@ -469,6 +925,174 @@ extension on TranslationsHi {
'deleteSchedule' => ({required Object name}) => '${name}?',
'addNotification' => 'अधिसूचना जोड़ें',
'empty_intakes' => 'ले लिया गया सेवन यहाँ दिखाई देगा',
+ 'HrtCounter' => 'HRT पर समय',
+ 'HrtCounterDescription' =>
+ 'जब तक आप एचआरटी और आपके कुल सेवन पर रहे हैं, तब तक दिखाएं',
+ 'hrtWidgetPlaceholder' => 'अपना पहला सेवन लॉग इन करने के लिए मोना खोलें',
+ 'hrtWidgetPreviewSample' => '8 महीने के लिए HRT पर',
+ 'hrtWidgetPreviewIntakeSample' => '16 सेवन लॉग इन',
+ 'startOfDayDescription' => ({required Object time}) =>
+ 'समय पहले ${time} पिछले दिन की ओर गिनती',
+ 'chooseSchedule' => 'एक अनुसूची चुनें',
+ 'addSchedulesFirst' => 'पहले शेड्यूल जोड़ें।।',
+ 'editIntake' => 'सेवन संपादित करें',
+ 'date' => 'तारीख',
+ 'amount' => 'राशि',
+ 'takenAmount' => 'ली गई राशि',
+ 'wastedAmount' => 'अपशिष्ट राशि',
+ 'none' => 'कोई नहीं',
+ 'startOfDay' => 'दिन की शुरुआत',
+ 'supplyItem' => 'आपूर्ति आइटम',
+ 'chooseItem' => 'एक आइटम चुनें',
+ 'noItemsToAdd' => 'उपलब्ध नहीं है',
+ 'injectionSide' => 'इंजेक्शन साइड',
+ 'deleteIntake' => 'इस सेवन को हटा दें?',
+ 'takeMedication' => ({required Object scheduleName}) => '${scheduleName}',
+ 'takeIntake' => 'सेवन करें',
+ 'intakeRecorded' => 'दर्ज करना',
+ 'needleDeadSpace' => 'सुई मृत अंतरिक्ष',
+ 'notes' => 'नोट',
+ 'microliters' => 'μL',
+ 'milliliters' => 'एमएल',
+ 'empty_levels' =>
+ 'एक रक्त परीक्षण जोड़ें या शुरू करने के लिए एक एस्ट्राडियोल इंजेक्शन लॉग इन करें',
+ 'bloodTestsTitle' => 'रक्त परीक्षण',
+ 'estradiolLevelsTitle' => 'एस्ट्राडियोल स्तर',
+ 'week' => 'डब्ल्यू',
+ 'twoWeeks' => '2 डब्ल्यू',
+ 'threeMonths' => '3 एम',
+ 'sixMonths' => '6 एम',
+ 'month' => 'एम',
+ 'year' => 'वाई',
+ 'empty_blood_tests' =>
+ 'रक्त परीक्षण यहाँ दिखाई देगा। ऐड बटन का उपयोग करके शुरू करें!',
+ 'addBloodTest' => 'रक्त परीक्षण जोड़ें',
+ 'editBloodTest' => 'रक्त परीक्षण',
+ 'newBloodTest' => 'न्यू ब्लड टेस्ट',
+ 'deleteBloodTest' => 'इस रक्त परीक्षण को हटा दें?',
+ 'estradiolLevelLabel' => 'एस्ट्राडियोल स्तर',
+ 'testosteroneLevelLabel' => 'टेस्टोस्टेरोन का स्तर',
+ 'bloodTestDateLabel' => 'परीक्षा तिथि',
+ 'chartNowConcentration' => ({required Object value}) => 'अब ${value}',
+ 'chartBloodTestLevelTooltip' =>
+ ({required Object date, required Object level}) => '${date}: ${level}',
+ 'chartLevelTooltip' => ({required Object date, required Object level}) =>
+ '${date}: ${level}',
+ 'empty_supplies' => 'कोई आपूर्ति नहीं। शुरू करने के लिए एक आइटम जोड़ें।।',
+ 'newItem' => 'नया आइटम',
+ 'adminRoute' => 'प्रशासन मार्ग',
+ 'totalAmount' => 'कुल राशि',
+ 'concentration' => 'एकाग्रता',
+ 'dosePerUnitLabel' => ({required Object unit}) => 'प्रति खुराक ${unit}',
+ 'editItem' => 'आइटम जोड़ें',
+ 'usedAmount' => 'प्रयुक्त राशि',
+ 'deleteItem' => ({required Object name}) => '${name}?',
+ 'allItemsFilter' => 'सब',
+ 'medicationItemsFilter' => 'दवा',
+ 'genericItems' => 'उपभोग्य',
+ 'medicationItemType' => 'दवा',
+ 'genericItemType' => 'उपभोग्य',
+ 'supplyType' => 'प्रकार',
+ 'syringe' => 'सिरिंज',
+ 'wipe' => 'वाइप्स',
+ 'needle' => 'सुई',
+ 'gloves' => 'दस्ताने',
+ 'bandage' => 'बंधन',
+ 'add' => 'जोड़ें',
+ 'save' => 'सहेजें',
+ 'cancel' => 'रद्द करना',
+ 'next' => 'अगला',
+ 'delete' => 'डिलीट',
+ 'deleteElement' => 'इस मद को हटाएं?',
+ 'irreversibleAction' => 'यह कार्रवाई नहीं हो सकती है।।',
+ 'name' => 'नाम',
+ 'molecule' => 'मोलेकुल',
+ 'ester' => 'एस्टर',
+ 'estradiol' => 'एस्ट्राडियोल',
+ 'progesterone' => 'प्रोजेस्टेरोन',
+ 'testosterone' => 'टेस्टोस्टेरोन',
+ 'nandrolone' => 'नांद्र',
+ 'dihydrotestosterone' => 'Dihydrotestosterone',
+ 'spironolactone' => 'Spironolactone',
+ 'cyproteroneAcetate' => 'Cyproterone एसीटेट',
+ 'leuprorelinAcetate' => 'ल्यूप्रोरेलिन एसीटेट',
+ 'bicalutamide' => 'Bicalutamide',
+ 'decapeptyl' => 'Decapeptyl',
+ 'raloxifene' => 'रालॉक्सीफेन',
+ 'tamoxifen' => 'टैमोक्सीफेन',
+ 'finasteride' => 'फिनस्टराइड',
+ 'dutasteride' => 'Dutasteride',
+ 'minoxidil' => 'मिनोक्सिडिल',
+ 'pioglitazone' => 'Pioglitazone',
+ 'enanthate' => 'Enanthate',
+ 'valerate' => 'वैलेरेट',
+ 'cypionate' => 'साइटमैप',
+ 'undecylate' => 'Undecylate',
+ 'benzoate' => 'बेंजोएट',
+ 'cypionateSuspension' => 'Cypionate निलंबन',
+ 'medicationEstradiolEnanthate' => 'Estradiol enanthate',
+ 'medicationEstradiolValerate' => 'Estradiol valerate',
+ 'medicationEstradiolCypionate' => 'Estradiol cypionate',
+ 'medicationEstradiolUndecylate' => 'Estradiol undecylate',
+ 'medicationEstradiolBenzoate' => 'Estradiol बेंजोएट',
+ 'medicationEstradiolCypionateSuspension' => 'Estradiol cypionate निलंबन',
+ 'medicationTestosteroneEnanthate' => 'टेस्टोस्टेरोन enanthate',
+ 'medicationTestosteroneValerate' => 'टेस्टोस्टेरोन valerate',
+ 'medicationTestosteroneCypionate' => 'टेस्टोस्टेरोन cypionate',
+ 'medicationTestosteroneUndecylate' => 'टेस्टोस्टेरोन undecylate',
+ 'medicationTestosteroneBenzoate' => 'टेस्टोस्टेरोन benzoate',
+ 'medicationTestosteroneCypionateSuspension' =>
+ 'टेस्टोस्टेरोन साइपीओनेट निलंबन',
+ 'injection' => 'इंजेक्शन',
+ 'oral' => 'मौखिक',
+ 'sublingual' => 'बहुभाषी',
+ 'patch' => 'पैच',
+ 'gel' => 'जेल',
+ 'implant' => 'प्रत्यारोपण',
+ 'suppository' => 'सपोसिटरी',
+ 'transdermalSpray' => 'ट्रांसडर्मल स्प्रे',
+ 'transdermalDrops' => 'ट्रांसडर्मल ड्रॉप',
+ 'deliveryForm' => 'फॉर्म',
+ 'deliveryFormPump' => 'पम्प',
+ 'deliveryFormSachet' => 'साचेत',
+ 'deliveryFormGram' => 'ट्यूब',
+ 'unitMilligram' => 'मिलीग्राम',
+ 'unitMicrogramPerDay' => 'μg / दिन',
+ 'unitPgPerMl' => 'pg/ml',
+ 'unitPmolPerL' => 'pmol',
+ 'unitNgPerDl' => 'ng/dl',
+ 'unitNmolPerL' => 'nmol/L',
+ 'unitNgPerMl' => 'एनजी / एमएल',
+ 'injectionSideLeft' => 'बाएं',
+ 'injectionSideRight' => 'अधिकार',
+ 'placementLeft' => 'बाएं तरफ',
+ 'placementRight' => 'सही पक्ष',
+ 'placementLeftThigh' => 'बाएं जांघ',
+ 'placementRightThigh' => 'सही जांघ',
+ 'placementLeftArm' => 'बाएं हाथ',
+ 'placementRightArm' => 'दाहिने हाथ',
+ 'placementLeftButtock' => 'बाएं बटॉक',
+ 'placementRightButtock' => 'राइट बटॉक',
+ 'placementLeftAbdomen' => 'बाएं पेट',
+ 'placementRightAbdomen' => 'दाहिने पेट',
+ 'applicationSites' => 'अनुप्रयोग साइट',
+ 'applicationSitesDescription' =>
+ 'उन साइटों को प्रबंधित करें जो आप के बीच घूमते हैं',
+ 'applicationSitesInstructions' =>
+ 'उन साइटों को प्रबंधित करें जो आप के बीच घूमते हैं। साइट्स को आपके सेवन इतिहास के आधार पर सुझाव दिया जाता है। फिर से व्यवस्थित करने के लिए लंबे प्रेस।।',
+ 'addApplicationSite' => 'साइट जोड़ें',
+ 'customSiteLabel' => 'कस्टम साइट नाम',
+ 'noApplicationSitesYet' => 'अभी तक कोई साइट नहीं',
+ 'addSiteToGetStarted' => 'शुरू करने के लिए नीचे एक साइट जोड़ें।।',
+ 'placementSuggestionPerScheduleTitle' => 'प्रति अनुसूची सुझाव',
+ 'placementSuggestionPerScheduleDescription' =>
+ 'इस कार्यक्रम के इतिहास पर अगले साइट सुझाव को बेस करें।।',
+ 'requiredField' => 'आवश्यक फ़ील्ड',
+ 'mustBePositiveNumber' => 'एक सकारात्मक संख्या होना चाहिए',
+ 'mustBeBetween1And28' => '1 और 28 के बीच होना चाहिए',
+ 'mustBeAtMost' => ({required Object max}) => '${max}',
+ 'invalidTotalAmount' => 'कुल राशि',
+ 'cannotExceedTotalCapacity' => 'कुल क्षमता से अधिक नहीं',
'daysAgoCount' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
count,
@@ -512,6 +1136,114 @@ extension on TranslationsHi {
one: '${count} महीनों के लिए HRT पर',
other: '${count} महीनों के लिए HRT पर',
),
+ 'onHrtForWeeks' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'HRT on ${count} week',
+ other: 'HRT on ${count} week',
+ ),
+ 'onHrtForYears' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'HRT पर ${count} वर्ष',
+ other: 'HRT पर ${count} वर्ष',
+ ),
+ 'intakesLoggedCount' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} प्रवेश लॉग इन',
+ other: '${count} प्रवेश लॉग इन',
+ ),
+ 'remaining' => ({required num count, required Object unit}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} ${unit} शेष',
+ other: '${count} ${unit} शेष',
+ ),
+ 'syringeRemaining' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} syringes शेष',
+ other: '${count} syringes शेष',
+ ),
+ 'wipeRemaining' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} शेष पोंछे',
+ other: '${count} शेष पोंछे',
+ ),
+ 'needleRemaining' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} सुई शेष',
+ other: '${count} सुई शेष',
+ ),
+ 'glovesRemaining' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} दस्ताने शेष',
+ other: '${count} दस्ताने शेष',
+ ),
+ 'bandageRemaining' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: '${count} बैंडेज शेष',
+ other: '${count} बैंडेज शेष',
+ ),
+ 'administrationRouteUnitMl' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'एमएल',
+ other: 'एमएल',
+ ),
+ 'administrationRouteUnitPill' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'गोलियाँ',
+ other: 'गोलियाँ',
+ ),
+ 'administrationRouteUnitPatch' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'पैच',
+ other: 'पैच',
+ ),
+ 'administrationRouteUnitPump' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'पंप',
+ other: 'पंप',
+ ),
+ 'administrationRouteUnitSachet' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'पाउच',
+ other: 'पाउच',
+ ),
+ 'administrationRouteUnitGram' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'ग्राम',
+ other: 'ग्राम',
+ ),
+ 'administrationRouteUnitImplant' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'प्रत्यारोपण',
+ other: 'प्रत्यारोपण',
+ ),
+ 'administrationRouteUnitSuppository' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'सट्टा',
+ other: 'सट्टा',
+ ),
+ 'administrationRouteUnitSpray' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hi'))(
+ count,
+ one: 'स्प्रे',
+ other: 'स्प्रे',
+ ),
_ => null,
};
}
diff --git a/lib/i18n/translations_hu.g.dart b/lib/i18n/translations_hu.g.dart
new file mode 100644
index 000000000..80517159e
--- /dev/null
+++ b/lib/i18n/translations_hu.g.dart
@@ -0,0 +1,138 @@
+///
+/// Generated file. Do not edit.
+///
+// coverage:ignore-file
+// ignore_for_file: type=lint, unused_import
+
+import 'package:flutter/widgets.dart';
+import 'package:intl/intl.dart';
+import 'package:slang/generated.dart';
+import 'translations.g.dart';
+
+// Path:
+class TranslationsHu extends Translations
+ with BaseTranslations {
+ /// You can call this constructor and build your own translation instance of this locale.
+ /// Constructing via the enum [AppLocale.build] is preferred.
+ TranslationsHu(
+ {Map? overrides,
+ PluralResolver? cardinalResolver,
+ PluralResolver? ordinalResolver,
+ TranslationMetadata? meta})
+ : assert(overrides == null,
+ 'Set "translation_overrides: true" in order to enable this feature.'),
+ $meta = meta ??
+ TranslationMetadata(
+ locale: AppLocale.hu,
+ overrides: overrides ?? {},
+ cardinalResolver: cardinalResolver,
+ ordinalResolver: ordinalResolver,
+ ),
+ super(
+ cardinalResolver: cardinalResolver,
+ ordinalResolver: ordinalResolver) {
+ super.$meta.setFlatMapFunction(
+ $meta.getTranslation); // copy base translations to super.$meta
+ $meta.setFlatMapFunction(_flatMapFunction);
+ }
+
+ /// Metadata for the translations of .
+ @override
+ final TranslationMetadata $meta;
+
+ /// Access flat map
+ @override
+ dynamic operator [](String key) =>
+ $meta.getTranslation(key) ?? super.$meta.getTranslation(key);
+
+ late final TranslationsHu _root = this; // ignore: unused_field
+
+ @override
+ TranslationsHu $copyWith(
+ {TranslationMetadata? meta}) =>
+ TranslationsHu(meta: meta ?? this.$meta);
+
+ // Translations
+ @override
+ String get appTitle => 'Mona';
+ @override
+ String get nav_home => 'Mona';
+ @override
+ String get nav_intakes => 'Bevevések';
+ @override
+ String get nav_levels => 'Szintek';
+ @override
+ String get nav_supplies => 'Készlet';
+ @override
+ String get takeAnIntake => 'Bevevés felírása';
+ @override
+ String get addAnItem => 'Item hozzáadása';
+ @override
+ String get empty_home =>
+ 'Kezdje egy időbeosztás hozzáadásával a Beállításokban';
+ @override
+ String get allDone => 'Kész!';
+ @override
+ String get noIntakesDue => 'Nincs mára felírt adag';
+ @override
+ String get upcoming => 'Következő';
+ @override
+ String get asNeeded => 'Ahogy szükséges';
+ @override
+ String get taken => 'Bevéve';
+ @override
+ String get yesterday => 'tegnap';
+ @override
+ String daysAgoCount({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hu'))(
+ count,
+ one: '${count} napja',
+ other: '${count} napja',
+ );
+ @override
+ String inDaysCount({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hu'))(
+ count,
+ one: '${count} nap múlva',
+ other: '${count} nap múlva',
+ );
+}
+
+/// The flat map containing all translations for locale .
+/// Only for edge cases! For simple maps, use the map function of this library.
+///
+/// The Dart AOT compiler has issues with very large switch statements,
+/// so the map is split into smaller functions (512 entries each).
+extension on TranslationsHu {
+ dynamic _flatMapFunction(String path) {
+ return switch (path) {
+ 'appTitle' => 'Mona',
+ 'nav_home' => 'Mona',
+ 'nav_intakes' => 'Bevevések',
+ 'nav_levels' => 'Szintek',
+ 'nav_supplies' => 'Készlet',
+ 'takeAnIntake' => 'Bevevés felírása',
+ 'addAnItem' => 'Item hozzáadása',
+ 'empty_home' => 'Kezdje egy időbeosztás hozzáadásával a Beállításokban',
+ 'allDone' => 'Kész!',
+ 'noIntakesDue' => 'Nincs mára felírt adag',
+ 'upcoming' => 'Következő',
+ 'asNeeded' => 'Ahogy szükséges',
+ 'taken' => 'Bevéve',
+ 'yesterday' => 'tegnap',
+ 'daysAgoCount' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hu'))(
+ count,
+ one: '${count} napja',
+ other: '${count} napja',
+ ),
+ 'inDaysCount' => ({required num count}) =>
+ (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('hu'))(
+ count,
+ one: '${count} nap múlva',
+ other: '${count} nap múlva',
+ ),
+ _ => null,
+ };
+ }
+}
diff --git a/lib/i18n/translations_id.g.dart b/lib/i18n/translations_id.g.dart
new file mode 100644
index 000000000..1f607cc70
--- /dev/null
+++ b/lib/i18n/translations_id.g.dart
@@ -0,0 +1,67 @@
+///
+/// Generated file. Do not edit.
+///
+// coverage:ignore-file
+// ignore_for_file: type=lint, unused_import
+
+import 'package:flutter/widgets.dart';
+import 'package:intl/intl.dart';
+import 'package:slang/generated.dart';
+import 'translations.g.dart';
+
+// Path:
+class TranslationsId extends Translations
+ with BaseTranslations {
+ /// You can call this constructor and build your own translation instance of this locale.
+ /// Constructing via the enum [AppLocale.build] is preferred.
+ TranslationsId(
+ {Map? overrides,
+ PluralResolver? cardinalResolver,
+ PluralResolver? ordinalResolver,
+ TranslationMetadata? meta})
+ : assert(overrides == null,
+ 'Set "translation_overrides: true" in order to enable this feature.'),
+ $meta = meta ??
+ TranslationMetadata(
+ locale: AppLocale.id,
+ overrides: overrides ?? {},
+ cardinalResolver: cardinalResolver,
+ ordinalResolver: ordinalResolver,
+ ),
+ super(
+ cardinalResolver: cardinalResolver,
+ ordinalResolver: ordinalResolver) {
+ super.$meta.setFlatMapFunction(
+ $meta.getTranslation); // copy base translations to super.$meta
+ $meta.setFlatMapFunction(_flatMapFunction);
+ }
+
+ /// Metadata for the translations of .
+ @override
+ final TranslationMetadata $meta;
+
+ /// Access flat map
+ @override
+ dynamic operator [](String key) =>
+ $meta.getTranslation(key) ?? super.$meta.getTranslation(key);
+
+ late final TranslationsId _root = this; // ignore: unused_field
+
+ @override
+ TranslationsId $copyWith(
+ {TranslationMetadata? meta}) =>
+ TranslationsId(meta: meta ?? this.$meta);
+
+ // Translations
+}
+
+/// The flat map containing all translations for locale .
+/// Only for edge cases! For simple maps, use the map function of this library.
+///
+/// The Dart AOT compiler has issues with very large switch statements,
+/// so the map is split into smaller functions (512 entries each).
+extension on TranslationsId {
+ dynamic _flatMapFunction(String path) {
+ ;
+ }
+}
diff --git a/lib/i18n/translations_pl.g.dart b/lib/i18n/translations_pl.g.dart
index 8b508bfa3..82b3b06c9 100644
--- a/lib/i18n/translations_pl.g.dart
+++ b/lib/i18n/translations_pl.g.dart
@@ -569,6 +569,7 @@ class TranslationsPl extends Translations
one: '${count} dzień temu',
few: '${count} dni temu',
many: '${count} dni temu',
+ other: '${count} dni temu',
);
@override
String inDaysCount({required num count}) =>
@@ -577,6 +578,7 @@ class TranslationsPl extends Translations
one: 'za ${count} dzień',
few: 'za ${count} dni',
many: 'za ${count} dni',
+ other: 'za ${count} dni',
);
@override
String scheduleFrequencyEveryNDays({required num count}) =>
@@ -585,6 +587,7 @@ class TranslationsPl extends Translations
one: 'Co dzień',
few: 'Co ${count} dni',
many: 'Co ${count} dni',
+ other: 'Co ${count} dni',
);
@override
String scheduleFrequencyOnDayEveryNMonths(
@@ -594,6 +597,7 @@ class TranslationsPl extends Translations
one: 'Dzień ${day}, co miesiąc',
few: 'Dzień ${day}, co ${count} miesiące',
many: 'Dzień ${day}, co ${count} miesięcy',
+ other: 'Dzień ${day}, co ${count} miesiące',
);
@override
String schedulesCreated({required num count}) =>
@@ -602,6 +606,7 @@ class TranslationsPl extends Translations
one: 'Utworzono: ${count}',
few: 'Utworzono: ${count}',
many: 'Utworzono: ${count}',
+ other: 'Utworzono: ${count}',
);
@override
String onHrtForDays({required num count}) =>
@@ -610,6 +615,7 @@ class TranslationsPl extends Translations
one: 'Na HTZ od 1 dnia',
few: 'Na HTZ od ${count} dni',
many: 'Na HTZ od ${count} dni',
+ other: 'Na HTZ od ${count} dni',
);
@override
String onHrtForWeeks({required num count}) =>
@@ -618,6 +624,7 @@ class TranslationsPl extends Translations
one: 'Na HTZ od 1 tygodnia',
few: 'Na HTZ od ${count} tygodni',
many: 'Na HTZ od ${count} tygodni',
+ other: 'Na HTZ od ${count} tygodni',
);
@override
String onHrtForMonths({required num count}) =>
@@ -626,6 +633,7 @@ class TranslationsPl extends Translations
one: 'Na HTZ od 1 miesiąca',
few: 'Na HTZ od ${count} miesięcy',
many: 'Na HTZ od ${count} miesięcy',
+ other: 'Na HTZ od ${count} miesięcy',
);
@override
String onHrtForYears({required num count}) =>
@@ -634,6 +642,7 @@ class TranslationsPl extends Translations
one: 'Na HTZ od 1 roku',
few: 'Na HTZ od ${count} lat',
many: 'Na HTZ od ${count} lat',
+ other: 'Na HTZ od ${count} lat',
);
@override
String intakesLoggedCount({required num count}) =>
@@ -642,6 +651,7 @@ class TranslationsPl extends Translations
one: 'Odnotowano 1 przyjęcie',
few: 'Odnotowano ${count} przyjęcia',
many: 'Odnotowano ${count} przyjęć',
+ other: 'Odnotowano ${count} przyjęcia',
);
@override
String remaining({required num count, required Object unit}) =>
@@ -650,6 +660,7 @@ class TranslationsPl extends Translations
one: 'Pozostało: ${count} ${unit}',
few: 'Pozostało: ${count} ${unit}',
many: 'Pozostało: ${count} ${unit}',
+ other: 'Pozostało: ${count} ${unit}',
);
@override
String syringeRemaining({required num count}) =>
@@ -658,6 +669,7 @@ class TranslationsPl extends Translations
one: 'Pozostało: 1 strzykawka',
few: 'Pozostało: ${count} strzykawki',
many: 'Pozostało: ${count} strzykawek',
+ other: 'Pozostało: ${count} strzykawki',
);
@override
String wipeRemaining({required num count}) =>
@@ -666,6 +678,7 @@ class TranslationsPl extends Translations
one: 'Pozostało: 1 chusteczka',
few: 'Pozostało: ${count} chusteczki',
many: 'Pozostało: ${count} chusteczek',
+ other: 'Pozostało: ${count} chusteczki',
);
@override
String needleRemaining({required num count}) =>
@@ -674,6 +687,7 @@ class TranslationsPl extends Translations
one: 'Pozostało: 1 igła',
few: 'Pozostało: ${count} igły',
many: 'Pozostało: ${count} igieł',
+ other: 'Pozostało: ${count} igły',
);
@override
String glovesRemaining({required num count}) =>
@@ -682,6 +696,7 @@ class TranslationsPl extends Translations
one: 'Pozostało: 1 rękawiczka',
few: 'Pozostało: ${count} rękawiczki',
many: 'Pozostało: ${count} rękawiczek',
+ other: 'Pozostało: ${count} rękawiczki',
);
@override
String bandageRemaining({required num count}) =>
@@ -690,6 +705,7 @@ class TranslationsPl extends Translations
one: 'Pozostało: 1 bandaż',
few: 'Pozostało: ${count} bandaże',
many: 'Pozostało: ${count} bandaży',
+ other: 'Pozostało: ${count} bandaże',
);
@override
String administrationRouteUnitMl({required num count}) =>
@@ -698,6 +714,7 @@ class TranslationsPl extends Translations
one: 'ml',
few: 'ml',
many: 'ml',
+ other: 'ml',
);
@override
String administrationRouteUnitPill({required num count}) =>
@@ -706,6 +723,7 @@ class TranslationsPl extends Translations
one: 'tabletka',
few: 'tabletki',
many: 'tabletek',
+ other: 'tabletki',
);
@override
String administrationRouteUnitPatch({required num count}) =>
@@ -714,6 +732,7 @@ class TranslationsPl extends Translations
one: 'plaster',
few: 'plastry',
many: 'plastrów',
+ other: 'plastry',
);
@override
String administrationRouteUnitPump({required num count}) =>
@@ -722,6 +741,7 @@ class TranslationsPl extends Translations
one: 'pompka',
few: 'pompki',
many: 'pompek',
+ other: 'pompki',
);
@override
String administrationRouteUnitImplant({required num count}) =>
@@ -730,6 +750,7 @@ class TranslationsPl extends Translations
one: 'implant',
few: 'implanty',
many: 'implantów',
+ other: 'implanty',
);
@override
String administrationRouteUnitSuppository({required num count}) =>
@@ -738,6 +759,7 @@ class TranslationsPl extends Translations
one: 'czopek',
few: 'czopki',
many: 'czopków',
+ other: 'czopki',
);
@override
String administrationRouteUnitSpray({required num count}) =>
@@ -746,6 +768,7 @@ class TranslationsPl extends Translations
one: 'spryśnięcie',
few: 'spryśnięcia',
many: 'spryśnięć',
+ other: 'spryśnięcia',
);
}
@@ -1031,6 +1054,7 @@ extension on TranslationsPl {
one: '${count} dzień temu',
few: '${count} dni temu',
many: '${count} dni temu',
+ other: '${count} dni temu',
),
'inDaysCount' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1038,6 +1062,7 @@ extension on TranslationsPl {
one: 'za ${count} dzień',
few: 'za ${count} dni',
many: 'za ${count} dni',
+ other: 'za ${count} dni',
),
'scheduleFrequencyEveryNDays' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1045,6 +1070,7 @@ extension on TranslationsPl {
one: 'Co dzień',
few: 'Co ${count} dni',
many: 'Co ${count} dni',
+ other: 'Co ${count} dni',
),
'scheduleFrequencyOnDayEveryNMonths' => (
{required num count, required Object day}) =>
@@ -1053,6 +1079,7 @@ extension on TranslationsPl {
one: 'Dzień ${day}, co miesiąc',
few: 'Dzień ${day}, co ${count} miesiące',
many: 'Dzień ${day}, co ${count} miesięcy',
+ other: 'Dzień ${day}, co ${count} miesiące',
),
'schedulesCreated' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1060,6 +1087,7 @@ extension on TranslationsPl {
one: 'Utworzono: ${count}',
few: 'Utworzono: ${count}',
many: 'Utworzono: ${count}',
+ other: 'Utworzono: ${count}',
),
'onHrtForDays' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1067,6 +1095,7 @@ extension on TranslationsPl {
one: 'Na HTZ od 1 dnia',
few: 'Na HTZ od ${count} dni',
many: 'Na HTZ od ${count} dni',
+ other: 'Na HTZ od ${count} dni',
),
'onHrtForWeeks' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1074,6 +1103,7 @@ extension on TranslationsPl {
one: 'Na HTZ od 1 tygodnia',
few: 'Na HTZ od ${count} tygodni',
many: 'Na HTZ od ${count} tygodni',
+ other: 'Na HTZ od ${count} tygodni',
),
'onHrtForMonths' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1081,6 +1111,7 @@ extension on TranslationsPl {
one: 'Na HTZ od 1 miesiąca',
few: 'Na HTZ od ${count} miesięcy',
many: 'Na HTZ od ${count} miesięcy',
+ other: 'Na HTZ od ${count} miesięcy',
),
'onHrtForYears' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1088,6 +1119,7 @@ extension on TranslationsPl {
one: 'Na HTZ od 1 roku',
few: 'Na HTZ od ${count} lat',
many: 'Na HTZ od ${count} lat',
+ other: 'Na HTZ od ${count} lat',
),
'intakesLoggedCount' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1095,6 +1127,7 @@ extension on TranslationsPl {
one: 'Odnotowano 1 przyjęcie',
few: 'Odnotowano ${count} przyjęcia',
many: 'Odnotowano ${count} przyjęć',
+ other: 'Odnotowano ${count} przyjęcia',
),
'remaining' => ({required num count, required Object unit}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1102,6 +1135,7 @@ extension on TranslationsPl {
one: 'Pozostało: ${count} ${unit}',
few: 'Pozostało: ${count} ${unit}',
many: 'Pozostało: ${count} ${unit}',
+ other: 'Pozostało: ${count} ${unit}',
),
'syringeRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1109,6 +1143,7 @@ extension on TranslationsPl {
one: 'Pozostało: 1 strzykawka',
few: 'Pozostało: ${count} strzykawki',
many: 'Pozostało: ${count} strzykawek',
+ other: 'Pozostało: ${count} strzykawki',
),
'wipeRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1116,6 +1151,7 @@ extension on TranslationsPl {
one: 'Pozostało: 1 chusteczka',
few: 'Pozostało: ${count} chusteczki',
many: 'Pozostało: ${count} chusteczek',
+ other: 'Pozostało: ${count} chusteczki',
),
'needleRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1123,6 +1159,7 @@ extension on TranslationsPl {
one: 'Pozostało: 1 igła',
few: 'Pozostało: ${count} igły',
many: 'Pozostało: ${count} igieł',
+ other: 'Pozostało: ${count} igły',
),
'glovesRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1130,6 +1167,7 @@ extension on TranslationsPl {
one: 'Pozostało: 1 rękawiczka',
few: 'Pozostało: ${count} rękawiczki',
many: 'Pozostało: ${count} rękawiczek',
+ other: 'Pozostało: ${count} rękawiczki',
),
'bandageRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1137,6 +1175,7 @@ extension on TranslationsPl {
one: 'Pozostało: 1 bandaż',
few: 'Pozostało: ${count} bandaże',
many: 'Pozostało: ${count} bandaży',
+ other: 'Pozostało: ${count} bandaże',
),
'administrationRouteUnitMl' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1144,6 +1183,7 @@ extension on TranslationsPl {
one: 'ml',
few: 'ml',
many: 'ml',
+ other: 'ml',
),
'administrationRouteUnitPill' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1151,6 +1191,7 @@ extension on TranslationsPl {
one: 'tabletka',
few: 'tabletki',
many: 'tabletek',
+ other: 'tabletki',
),
'administrationRouteUnitPatch' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1158,6 +1199,7 @@ extension on TranslationsPl {
one: 'plaster',
few: 'plastry',
many: 'plastrów',
+ other: 'plastry',
),
'administrationRouteUnitPump' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1165,6 +1207,7 @@ extension on TranslationsPl {
one: 'pompka',
few: 'pompki',
many: 'pompek',
+ other: 'pompki',
),
'administrationRouteUnitImplant' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1172,6 +1215,7 @@ extension on TranslationsPl {
one: 'implant',
few: 'implanty',
many: 'implantów',
+ other: 'implanty',
),
'administrationRouteUnitSuppository' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1179,6 +1223,7 @@ extension on TranslationsPl {
one: 'czopek',
few: 'czopki',
many: 'czopków',
+ other: 'czopki',
),
'administrationRouteUnitSpray' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(
@@ -1186,6 +1231,7 @@ extension on TranslationsPl {
one: 'spryśnięcie',
few: 'spryśnięcia',
many: 'spryśnięć',
+ other: 'spryśnięcia',
),
_ => null,
};
diff --git a/lib/i18n/translations_ru.g.dart b/lib/i18n/translations_ru.g.dart
index a9c76f733..16e8d0eb8 100644
--- a/lib/i18n/translations_ru.g.dart
+++ b/lib/i18n/translations_ru.g.dart
@@ -565,6 +565,7 @@ class TranslationsRu extends Translations
one: '${count} день назад',
few: '${count} дня назад',
many: '${count} дней назад',
+ other: '${count} дня назад',
);
@override
String inDaysCount({required num count}) =>
@@ -573,6 +574,7 @@ class TranslationsRu extends Translations
one: 'через ${count} день',
few: 'через ${count} дня',
many: 'через ${count} дней',
+ other: 'через ${count} дня',
);
@override
String scheduleFrequencyEveryNDays({required num count}) =>
@@ -581,6 +583,7 @@ class TranslationsRu extends Translations
one: 'Каждые ${count} дней',
few: 'Каждые ${count} дней',
many: 'Каждые ${count} дней',
+ other: 'Каждые ${count} дней',
);
@override
String schedulesCreated({required num count}) =>
@@ -589,6 +592,7 @@ class TranslationsRu extends Translations
one: '${count} создано',
few: '${count} создано',
many: '${count} создано',
+ other: '${count} создано',
);
@override
String remaining({required num count, required Object unit}) =>
@@ -597,6 +601,7 @@ class TranslationsRu extends Translations
one: '${count} ${unit} осталось',
few: '${count} ${unit} осталось',
many: '${count} ${unit} осталось',
+ other: '${count} ${unit} осталось',
);
@override
String syringeRemaining({required num count}) =>
@@ -605,6 +610,7 @@ class TranslationsRu extends Translations
one: '1 шприц осталось',
few: '${count} шприца осталось',
many: '${count} шприцев осталось',
+ other: '${count} шприца осталось',
);
@override
String wipeRemaining({required num count}) =>
@@ -613,6 +619,7 @@ class TranslationsRu extends Translations
one: '1 салфетка осталось',
few: '${count} салфетки осталось',
many: '${count} салфеток осталось',
+ other: '${count} салфетки осталось',
);
@override
String needleRemaining({required num count}) =>
@@ -621,6 +628,7 @@ class TranslationsRu extends Translations
one: '1 игла осталось',
few: '${count} иглы осталось',
many: '${count} игл осталось',
+ other: '${count} иглы осталось',
);
@override
String glovesRemaining({required num count}) =>
@@ -629,6 +637,7 @@ class TranslationsRu extends Translations
one: '1 перчатка осталось',
few: '${count} перчатки осталось',
many: '${count} перчаток осталось',
+ other: '${count} перчатки осталось',
);
@override
String bandageRemaining({required num count}) =>
@@ -637,6 +646,7 @@ class TranslationsRu extends Translations
one: '1 пластырь осталось',
few: '${count} пластыря осталось',
many: '${count} пластырей осталось',
+ other: '${count} пластыря осталось',
);
@override
String administrationRouteUnitMl({required num count}) =>
@@ -645,6 +655,7 @@ class TranslationsRu extends Translations
one: 'мл',
few: 'мл',
many: 'мл',
+ other: 'мл',
);
@override
String administrationRouteUnitPill({required num count}) =>
@@ -653,6 +664,7 @@ class TranslationsRu extends Translations
one: 'таблетка',
few: 'таблетки',
many: 'таблеток',
+ other: 'таблетки',
);
@override
String administrationRouteUnitPatch({required num count}) =>
@@ -661,6 +673,7 @@ class TranslationsRu extends Translations
one: 'пластырь',
few: 'пластыря',
many: 'пластырей',
+ other: 'пластыря',
);
@override
String administrationRouteUnitPump({required num count}) =>
@@ -669,6 +682,7 @@ class TranslationsRu extends Translations
one: 'помпа',
few: 'помпы',
many: 'помп',
+ other: 'помпы',
);
@override
String administrationRouteUnitImplant({required num count}) =>
@@ -677,6 +691,7 @@ class TranslationsRu extends Translations
one: 'имплант',
few: 'импланта',
many: 'имплантов',
+ other: 'импланта',
);
@override
String administrationRouteUnitSuppository({required num count}) =>
@@ -685,6 +700,7 @@ class TranslationsRu extends Translations
one: 'суппозитория',
few: 'суппозитории',
many: 'суппозиторий',
+ other: 'суппозитории',
);
@override
String administrationRouteUnitSpray({required num count}) =>
@@ -693,6 +709,7 @@ class TranslationsRu extends Translations
one: 'брызг',
few: 'брызг',
many: 'брызгов',
+ other: 'брызг',
);
@override
String scheduleFrequencyOnDayEveryNMonths(
@@ -702,6 +719,7 @@ class TranslationsRu extends Translations
one: 'День ${day}, каждый ${count} месяц',
few: 'День ${day}, каждые ${count} месяца',
many: 'День ${day}, каждые ${count} месяцев',
+ other: 'День ${day}, каждые ${count} месяца',
);
@override
String onHrtForDays({required num count}) =>
@@ -710,6 +728,7 @@ class TranslationsRu extends Translations
one: 'На ЗГТ уже ${count} день',
few: 'На ЗГТ уже ${count} дня',
many: 'На ЗГТ уже ${count} дней',
+ other: 'На ЗГТ уже ${count} дня',
);
@override
String onHrtForWeeks({required num count}) =>
@@ -718,6 +737,7 @@ class TranslationsRu extends Translations
one: 'На ЗГТ уже ${count} неделю',
few: 'На ЗГТ уже ${count} недели',
many: 'На ЗГТ уже ${count} недель',
+ other: 'На ЗГТ уже ${count} недели',
);
@override
String onHrtForMonths({required num count}) =>
@@ -726,6 +746,7 @@ class TranslationsRu extends Translations
one: 'На ЗГТ уже ${count} месяц',
few: 'На ЗГТ уже ${count} месяца',
many: 'На ЗГТ уже ${count} месяцев',
+ other: 'На ЗГТ уже ${count} месяца',
);
@override
String onHrtForYears({required num count}) =>
@@ -734,6 +755,7 @@ class TranslationsRu extends Translations
one: 'На ЗГТ уже ${count} год',
few: 'На ЗГТ уже ${count} года',
many: 'На ЗГТ уже ${count} лет',
+ other: 'На ЗГТ уже ${count} года',
);
@override
String intakesLoggedCount({required num count}) =>
@@ -742,6 +764,7 @@ class TranslationsRu extends Translations
one: 'Записан ${count} приём',
few: 'Записано ${count} приёма',
many: 'Записано ${count} приёмов',
+ other: 'Записано ${count} приёма',
);
}
@@ -1025,6 +1048,7 @@ extension on TranslationsRu {
one: '${count} день назад',
few: '${count} дня назад',
many: '${count} дней назад',
+ other: '${count} дня назад',
),
'inDaysCount' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1032,6 +1056,7 @@ extension on TranslationsRu {
one: 'через ${count} день',
few: 'через ${count} дня',
many: 'через ${count} дней',
+ other: 'через ${count} дня',
),
'scheduleFrequencyEveryNDays' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1039,6 +1064,7 @@ extension on TranslationsRu {
one: 'Каждые ${count} дней',
few: 'Каждые ${count} дней',
many: 'Каждые ${count} дней',
+ other: 'Каждые ${count} дней',
),
'schedulesCreated' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1046,6 +1072,7 @@ extension on TranslationsRu {
one: '${count} создано',
few: '${count} создано',
many: '${count} создано',
+ other: '${count} создано',
),
'remaining' => ({required num count, required Object unit}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1053,6 +1080,7 @@ extension on TranslationsRu {
one: '${count} ${unit} осталось',
few: '${count} ${unit} осталось',
many: '${count} ${unit} осталось',
+ other: '${count} ${unit} осталось',
),
'syringeRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1060,6 +1088,7 @@ extension on TranslationsRu {
one: '1 шприц осталось',
few: '${count} шприца осталось',
many: '${count} шприцев осталось',
+ other: '${count} шприца осталось',
),
'wipeRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1067,6 +1096,7 @@ extension on TranslationsRu {
one: '1 салфетка осталось',
few: '${count} салфетки осталось',
many: '${count} салфеток осталось',
+ other: '${count} салфетки осталось',
),
'needleRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1074,6 +1104,7 @@ extension on TranslationsRu {
one: '1 игла осталось',
few: '${count} иглы осталось',
many: '${count} игл осталось',
+ other: '${count} иглы осталось',
),
'glovesRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1081,6 +1112,7 @@ extension on TranslationsRu {
one: '1 перчатка осталось',
few: '${count} перчатки осталось',
many: '${count} перчаток осталось',
+ other: '${count} перчатки осталось',
),
'bandageRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1088,6 +1120,7 @@ extension on TranslationsRu {
one: '1 пластырь осталось',
few: '${count} пластыря осталось',
many: '${count} пластырей осталось',
+ other: '${count} пластыря осталось',
),
'administrationRouteUnitMl' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1095,6 +1128,7 @@ extension on TranslationsRu {
one: 'мл',
few: 'мл',
many: 'мл',
+ other: 'мл',
),
'administrationRouteUnitPill' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1102,6 +1136,7 @@ extension on TranslationsRu {
one: 'таблетка',
few: 'таблетки',
many: 'таблеток',
+ other: 'таблетки',
),
'administrationRouteUnitPatch' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1109,6 +1144,7 @@ extension on TranslationsRu {
one: 'пластырь',
few: 'пластыря',
many: 'пластырей',
+ other: 'пластыря',
),
'administrationRouteUnitPump' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1116,6 +1152,7 @@ extension on TranslationsRu {
one: 'помпа',
few: 'помпы',
many: 'помп',
+ other: 'помпы',
),
'administrationRouteUnitImplant' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1123,6 +1160,7 @@ extension on TranslationsRu {
one: 'имплант',
few: 'импланта',
many: 'имплантов',
+ other: 'импланта',
),
'administrationRouteUnitSuppository' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1130,6 +1168,7 @@ extension on TranslationsRu {
one: 'суппозитория',
few: 'суппозитории',
many: 'суппозиторий',
+ other: 'суппозитории',
),
'administrationRouteUnitSpray' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1137,6 +1176,7 @@ extension on TranslationsRu {
one: 'брызг',
few: 'брызг',
many: 'брызгов',
+ other: 'брызг',
),
'scheduleFrequencyOnDayEveryNMonths' => (
{required num count, required Object day}) =>
@@ -1145,6 +1185,7 @@ extension on TranslationsRu {
one: 'День ${day}, каждый ${count} месяц',
few: 'День ${day}, каждые ${count} месяца',
many: 'День ${day}, каждые ${count} месяцев',
+ other: 'День ${day}, каждые ${count} месяца',
),
'onHrtForDays' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1152,6 +1193,7 @@ extension on TranslationsRu {
one: 'На ЗГТ уже ${count} день',
few: 'На ЗГТ уже ${count} дня',
many: 'На ЗГТ уже ${count} дней',
+ other: 'На ЗГТ уже ${count} дня',
),
'onHrtForWeeks' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1159,6 +1201,7 @@ extension on TranslationsRu {
one: 'На ЗГТ уже ${count} неделю',
few: 'На ЗГТ уже ${count} недели',
many: 'На ЗГТ уже ${count} недель',
+ other: 'На ЗГТ уже ${count} недели',
),
'onHrtForMonths' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1166,6 +1209,7 @@ extension on TranslationsRu {
one: 'На ЗГТ уже ${count} месяц',
few: 'На ЗГТ уже ${count} месяца',
many: 'На ЗГТ уже ${count} месяцев',
+ other: 'На ЗГТ уже ${count} месяца',
),
'onHrtForYears' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1173,6 +1217,7 @@ extension on TranslationsRu {
one: 'На ЗГТ уже ${count} год',
few: 'На ЗГТ уже ${count} года',
many: 'На ЗГТ уже ${count} лет',
+ other: 'На ЗГТ уже ${count} года',
),
'intakesLoggedCount' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(
@@ -1180,6 +1225,7 @@ extension on TranslationsRu {
one: 'Записан ${count} приём',
few: 'Записано ${count} приёма',
many: 'Записано ${count} приёмов',
+ other: 'Записано ${count} приёма',
),
_ => null,
};
diff --git a/lib/i18n/translations_uk.g.dart b/lib/i18n/translations_uk.g.dart
index 08809f4fa..aef351520 100644
--- a/lib/i18n/translations_uk.g.dart
+++ b/lib/i18n/translations_uk.g.dart
@@ -566,6 +566,7 @@ class TranslationsUk extends Translations
one: '${count} день тому',
few: '${count} дні тому',
many: '${count} днів тому',
+ other: '${count} дні тому',
);
@override
String inDaysCount({required num count}) =>
@@ -574,6 +575,7 @@ class TranslationsUk extends Translations
one: 'через ${count} день',
few: 'через ${count} дні',
many: 'через ${count} днів',
+ other: 'через ${count} дні',
);
@override
String scheduleFrequencyEveryNDays({required num count}) =>
@@ -582,6 +584,7 @@ class TranslationsUk extends Translations
one: 'Кожні ${count} днів',
few: 'Кожні ${count} днів',
many: 'Кожні ${count} днів',
+ other: 'Кожні ${count} днів',
);
@override
String schedulesCreated({required num count}) =>
@@ -590,6 +593,7 @@ class TranslationsUk extends Translations
one: 'Створено ${count} розклад',
few: 'Створено ${count} розклади',
many: 'Створено ${count} розкладів',
+ other: 'Створено ${count} розклади',
);
@override
String remaining({required num count, required Object unit}) =>
@@ -598,6 +602,7 @@ class TranslationsUk extends Translations
one: '${count} ${unit} залишилось',
few: '${count} ${unit} залишилось',
many: '${count} ${unit} залишилось',
+ other: '${count} ${unit} залишилось',
);
@override
String syringeRemaining({required num count}) =>
@@ -606,6 +611,7 @@ class TranslationsUk extends Translations
one: '1 шприц залишилось',
few: '${count} шприца залишилось',
many: '${count} шприців залишилось',
+ other: '${count} шприца залишилось',
);
@override
String wipeRemaining({required num count}) =>
@@ -614,6 +620,7 @@ class TranslationsUk extends Translations
one: '1 серветка залишилось',
few: '${count} серветки залишилось',
many: '${count} серветок залишилось',
+ other: '${count} серветки залишилось',
);
@override
String needleRemaining({required num count}) =>
@@ -622,6 +629,7 @@ class TranslationsUk extends Translations
one: '1 голка залишилось',
few: '${count} голки залишилось',
many: '${count} голок залишилось',
+ other: '${count} голки залишилось',
);
@override
String glovesRemaining({required num count}) =>
@@ -630,6 +638,7 @@ class TranslationsUk extends Translations
one: '1 рукавичка залишилось',
few: '${count} рукавички залишилось',
many: '${count} рукавичок залишилось',
+ other: '${count} рукавички залишилось',
);
@override
String bandageRemaining({required num count}) =>
@@ -638,6 +647,7 @@ class TranslationsUk extends Translations
one: '1 пластир залишилось',
few: '${count} пластирі залишилось',
many: '${count} пластирів залишилось',
+ other: '${count} пластирі залишилось',
);
@override
String administrationRouteUnitMl({required num count}) =>
@@ -646,6 +656,7 @@ class TranslationsUk extends Translations
one: 'ml',
few: 'ml',
many: 'ml',
+ other: 'ml',
);
@override
String administrationRouteUnitPill({required num count}) =>
@@ -654,6 +665,7 @@ class TranslationsUk extends Translations
one: 'пігулка',
few: 'пігулки',
many: 'пігулок',
+ other: 'пігулки',
);
@override
String administrationRouteUnitPatch({required num count}) =>
@@ -662,6 +674,7 @@ class TranslationsUk extends Translations
one: 'патч',
few: 'патчі',
many: 'патчів',
+ other: 'патчі',
);
@override
String administrationRouteUnitPump({required num count}) =>
@@ -670,6 +683,7 @@ class TranslationsUk extends Translations
one: 'натискання',
few: 'натискання',
many: 'натискань',
+ other: 'натискання',
);
@override
String administrationRouteUnitImplant({required num count}) =>
@@ -678,6 +692,7 @@ class TranslationsUk extends Translations
one: 'імплант',
few: 'імпланти',
many: 'імплантів',
+ other: 'імпланти',
);
@override
String administrationRouteUnitSuppository({required num count}) =>
@@ -686,6 +701,7 @@ class TranslationsUk extends Translations
one: 'супозиторій',
few: 'супозиторії',
many: 'супозиторіїв',
+ other: 'супозиторії',
);
@override
String administrationRouteUnitSpray({required num count}) =>
@@ -694,6 +710,7 @@ class TranslationsUk extends Translations
one: 'спрей',
few: 'спреї',
many: 'спреїв',
+ other: 'спреї',
);
@override
String scheduleFrequencyOnDayEveryNMonths(
@@ -703,6 +720,7 @@ class TranslationsUk extends Translations
one: 'День ${day}, кожен ${count} місяць',
few: 'День ${day}, кожні ${count} місяці',
many: 'День ${day}, кожні ${count} місяців',
+ other: 'День ${day}, кожні ${count} місяці',
);
@override
String onHrtForDays({required num count}) =>
@@ -711,6 +729,7 @@ class TranslationsUk extends Translations
one: 'На ЗГТ вже ${count} день',
few: 'На ЗГТ вже ${count} дні',
many: 'На ЗГТ вже ${count} днів',
+ other: 'На ЗГТ вже ${count} дні',
);
@override
String onHrtForWeeks({required num count}) =>
@@ -719,6 +738,7 @@ class TranslationsUk extends Translations
one: 'На ЗГТ вже ${count} тиждень',
few: 'На ЗГТ вже ${count} тижні',
many: 'На ЗГТ вже ${count} тижнів',
+ other: 'На ЗГТ вже ${count} тижні',
);
@override
String onHrtForMonths({required num count}) =>
@@ -727,6 +747,7 @@ class TranslationsUk extends Translations
one: 'На ЗГТ вже ${count} місяць',
few: 'На ЗГТ вже ${count} місяці',
many: 'На ЗГТ вже ${count} місяців',
+ other: 'На ЗГТ вже ${count} місяці',
);
@override
String onHrtForYears({required num count}) =>
@@ -735,6 +756,7 @@ class TranslationsUk extends Translations
one: 'На ЗГТ вже ${count} рік',
few: 'На ЗГТ вже ${count} роки',
many: 'На ЗГТ вже ${count} років',
+ other: 'На ЗГТ вже ${count} роки',
);
@override
String intakesLoggedCount({required num count}) =>
@@ -743,6 +765,7 @@ class TranslationsUk extends Translations
one: 'Записано ${count} прийом',
few: 'Записано ${count} прийоми',
many: 'Записано ${count} прийомів',
+ other: 'Записано ${count} прийоми',
);
}
@@ -1025,6 +1048,7 @@ extension on TranslationsUk {
one: '${count} день тому',
few: '${count} дні тому',
many: '${count} днів тому',
+ other: '${count} дні тому',
),
'inDaysCount' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1032,6 +1056,7 @@ extension on TranslationsUk {
one: 'через ${count} день',
few: 'через ${count} дні',
many: 'через ${count} днів',
+ other: 'через ${count} дні',
),
'scheduleFrequencyEveryNDays' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1039,6 +1064,7 @@ extension on TranslationsUk {
one: 'Кожні ${count} днів',
few: 'Кожні ${count} днів',
many: 'Кожні ${count} днів',
+ other: 'Кожні ${count} днів',
),
'schedulesCreated' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1046,6 +1072,7 @@ extension on TranslationsUk {
one: 'Створено ${count} розклад',
few: 'Створено ${count} розклади',
many: 'Створено ${count} розкладів',
+ other: 'Створено ${count} розклади',
),
'remaining' => ({required num count, required Object unit}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1053,6 +1080,7 @@ extension on TranslationsUk {
one: '${count} ${unit} залишилось',
few: '${count} ${unit} залишилось',
many: '${count} ${unit} залишилось',
+ other: '${count} ${unit} залишилось',
),
'syringeRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1060,6 +1088,7 @@ extension on TranslationsUk {
one: '1 шприц залишилось',
few: '${count} шприца залишилось',
many: '${count} шприців залишилось',
+ other: '${count} шприца залишилось',
),
'wipeRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1067,6 +1096,7 @@ extension on TranslationsUk {
one: '1 серветка залишилось',
few: '${count} серветки залишилось',
many: '${count} серветок залишилось',
+ other: '${count} серветки залишилось',
),
'needleRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1074,6 +1104,7 @@ extension on TranslationsUk {
one: '1 голка залишилось',
few: '${count} голки залишилось',
many: '${count} голок залишилось',
+ other: '${count} голки залишилось',
),
'glovesRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1081,6 +1112,7 @@ extension on TranslationsUk {
one: '1 рукавичка залишилось',
few: '${count} рукавички залишилось',
many: '${count} рукавичок залишилось',
+ other: '${count} рукавички залишилось',
),
'bandageRemaining' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1088,6 +1120,7 @@ extension on TranslationsUk {
one: '1 пластир залишилось',
few: '${count} пластирі залишилось',
many: '${count} пластирів залишилось',
+ other: '${count} пластирі залишилось',
),
'administrationRouteUnitMl' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1095,6 +1128,7 @@ extension on TranslationsUk {
one: 'ml',
few: 'ml',
many: 'ml',
+ other: 'ml',
),
'administrationRouteUnitPill' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1102,6 +1136,7 @@ extension on TranslationsUk {
one: 'пігулка',
few: 'пігулки',
many: 'пігулок',
+ other: 'пігулки',
),
'administrationRouteUnitPatch' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1109,6 +1144,7 @@ extension on TranslationsUk {
one: 'патч',
few: 'патчі',
many: 'патчів',
+ other: 'патчі',
),
'administrationRouteUnitPump' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1116,6 +1152,7 @@ extension on TranslationsUk {
one: 'натискання',
few: 'натискання',
many: 'натискань',
+ other: 'натискання',
),
'administrationRouteUnitImplant' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1123,6 +1160,7 @@ extension on TranslationsUk {
one: 'імплант',
few: 'імпланти',
many: 'імплантів',
+ other: 'імпланти',
),
'administrationRouteUnitSuppository' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1130,6 +1168,7 @@ extension on TranslationsUk {
one: 'супозиторій',
few: 'супозиторії',
many: 'супозиторіїв',
+ other: 'супозиторії',
),
'administrationRouteUnitSpray' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1137,6 +1176,7 @@ extension on TranslationsUk {
one: 'спрей',
few: 'спреї',
many: 'спреїв',
+ other: 'спреї',
),
'scheduleFrequencyOnDayEveryNMonths' => (
{required num count, required Object day}) =>
@@ -1145,6 +1185,7 @@ extension on TranslationsUk {
one: 'День ${day}, кожен ${count} місяць',
few: 'День ${day}, кожні ${count} місяці',
many: 'День ${day}, кожні ${count} місяців',
+ other: 'День ${day}, кожні ${count} місяці',
),
'onHrtForDays' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1152,6 +1193,7 @@ extension on TranslationsUk {
one: 'На ЗГТ вже ${count} день',
few: 'На ЗГТ вже ${count} дні',
many: 'На ЗГТ вже ${count} днів',
+ other: 'На ЗГТ вже ${count} дні',
),
'onHrtForWeeks' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1159,6 +1201,7 @@ extension on TranslationsUk {
one: 'На ЗГТ вже ${count} тиждень',
few: 'На ЗГТ вже ${count} тижні',
many: 'На ЗГТ вже ${count} тижнів',
+ other: 'На ЗГТ вже ${count} тижні',
),
'onHrtForMonths' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1166,6 +1209,7 @@ extension on TranslationsUk {
one: 'На ЗГТ вже ${count} місяць',
few: 'На ЗГТ вже ${count} місяці',
many: 'На ЗГТ вже ${count} місяців',
+ other: 'На ЗГТ вже ${count} місяці',
),
'onHrtForYears' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1173,6 +1217,7 @@ extension on TranslationsUk {
one: 'На ЗГТ вже ${count} рік',
few: 'На ЗГТ вже ${count} роки',
many: 'На ЗГТ вже ${count} років',
+ other: 'На ЗГТ вже ${count} роки',
),
'intakesLoggedCount' => ({required num count}) =>
(_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('uk'))(
@@ -1180,6 +1225,7 @@ extension on TranslationsUk {
one: 'Записано ${count} прийом',
few: 'Записано ${count} прийоми',
many: 'Записано ${count} прийомів',
+ other: 'Записано ${count} прийоми',
),
_ => null,
};
diff --git a/lib/services/db/app_database.dart b/lib/services/db/app_database.dart
index cadecc9b5..e75b9eda5 100644
--- a/lib/services/db/app_database.dart
+++ b/lib/services/db/app_database.dart
@@ -15,6 +15,7 @@ import 'package:mona/services/db/upgrade/v19.dart';
import 'package:mona/services/db/upgrade/v2.dart';
import 'package:mona/services/db/upgrade/v20.dart';
import 'package:mona/services/db/upgrade/v21.dart';
+import 'package:mona/services/db/upgrade/v22.dart';
import 'package:mona/services/db/upgrade/v3.dart';
import 'package:mona/services/db/upgrade/v4.dart';
import 'package:mona/services/db/upgrade/v5.dart';
@@ -25,7 +26,7 @@ import 'package:mona/services/db/upgrade/v9.dart';
import 'package:path/path.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
-const int currentDatabaseVersion = 21;
+const int currentDatabaseVersion = 22;
final Map _upgrades = {
2: DbUpgradeV2(),
@@ -48,6 +49,7 @@ final Map _upgrades = {
19: DbUpgradeV19(),
20: DbUpgradeV20(),
21: DbUpgradeV21(),
+ 22: DbUpgradeV22(),
};
class AppDatabase {
diff --git a/lib/services/db/db_tables.dart b/lib/services/db/db_tables.dart
index 067619abc..553185831 100644
--- a/lib/services/db/db_tables.dart
+++ b/lib/services/db/db_tables.dart
@@ -34,6 +34,7 @@ const String createMedicationIntakesTable = '''
notes TEXT,
placements TEXT NOT NULL,
dosingBasis TEXT NOT NULL,
+ injectionType TEXT,
FOREIGN KEY (medicationSupplyItemId) REFERENCES supply_items(id) ON DELETE SET NULL,
FOREIGN KEY (scheduleId) REFERENCES medication_schedules(id) ON DELETE SET NULL
)
diff --git a/lib/services/db/historical_schemas.dart b/lib/services/db/historical_schemas.dart
index 25e8a63e3..6883ce9e0 100644
--- a/lib/services/db/historical_schemas.dart
+++ b/lib/services/db/historical_schemas.dart
@@ -421,6 +421,30 @@ const String _medicationSchedulesV21 = '''
)
''';
+const String _medicationIntakesV22 = '''
+ CREATE TABLE medication_intakes(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ scheduledTime TEXT,
+ takenDateTime TEXT,
+ takenTimeZone TEXT,
+ takenDose TEXT NOT NULL,
+ wastedAmount TEXT,
+ deadSpace TEXT,
+ scheduleId INTEGER,
+ molecule TEXT NOT NULL,
+ administrationRoute TEXT NOT NULL,
+ ester TEXT,
+ medicationSupplyItemId INTEGER,
+ genericSupplyItemIds TEXT NOT NULL,
+ notes TEXT,
+ placements TEXT NOT NULL,
+ dosingBasis TEXT NOT NULL,
+ injectionType TEXT,
+ FOREIGN KEY (medicationSupplyItemId) REFERENCES supply_items(id) ON DELETE SET NULL,
+ FOREIGN KEY (scheduleId) REFERENCES medication_schedules(id) ON DELETE SET NULL
+ )
+ ''';
+
const Map> _historicalSchemas = {
4: [
_supplyItemsV4,
@@ -530,6 +554,12 @@ const Map> _historicalSchemas = {
_medicationSchedulesV21,
_bloodTestsV20,
],
+ 22: [
+ _supplyItemsV21,
+ _medicationIntakesV22,
+ _medicationSchedulesV21,
+ _bloodTestsV20,
+ ],
};
List historicalSchemaFor(int version) {
diff --git a/lib/services/db/upgrade/v22.dart b/lib/services/db/upgrade/v22.dart
new file mode 100644
index 000000000..9af39f0e8
--- /dev/null
+++ b/lib/services/db/upgrade/v22.dart
@@ -0,0 +1,59 @@
+import 'package:mona/services/db/upgrade/db_upgrade.dart';
+
+import 'package:sqflite/sqlite_api.dart';
+
+class DbUpgradeV22 implements DbUpgrade {
+ @override
+ Future upgrade(Database db, int oldVersion, int newVersion) async {
+ await db.execute('''
+ ALTER TABLE medication_intakes
+ ADD COLUMN injectionType TEXT
+ ''');
+
+ await db.execute('''
+ UPDATE medication_intakes
+ SET injectionType = 'intramuscular'
+ WHERE administrationRoute = 'injection'
+ ''');
+
+ await _repairDoubleWrappedLevels(db);
+ }
+
+ // repair v18 being ran on already-wrapped entries
+ Future _repairDoubleWrappedLevels(Database db) async {
+ const columns = ['estradiolLevels', 'testosteroneLevels'];
+ final rows = await db.query('blood_tests', columns: ['id', ...columns]);
+
+ for (final row in rows) {
+ final fixes = {};
+ for (final column in columns) {
+ final value = row[column] as String?;
+ final unwrapped = _unwrapLevel(value);
+ if (unwrapped != value) {
+ fixes[column] = unwrapped;
+ }
+ }
+ if (fixes.isNotEmpty) {
+ await db.update('blood_tests', fixes,
+ where: 'id = ?', whereArgs: [row['id']]);
+ }
+ }
+ }
+
+ static String? _unwrapLevel(String? value) {
+ if (value == null || !value.startsWith('{"value":"{')) return value;
+
+ final innerStart = value.indexOf('{', 1);
+ var depth = 0;
+ for (var i = innerStart; i < value.length; i++) {
+ final char = value[i];
+ if (char == '{') {
+ depth++;
+ } else if (char == '}') {
+ depth--;
+ if (depth == 0) return value.substring(innerStart, i + 1);
+ }
+ }
+ return value;
+ }
+}
diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart
index d1198f157..c5ffac8a1 100644
--- a/lib/services/notification_service.dart
+++ b/lib/services/notification_service.dart
@@ -9,6 +9,13 @@ import 'package:mona/util/string_parsing.dart';
import 'package:timezone/data/latest_all.dart' as tzdata;
import 'package:timezone/timezone.dart' as tz;
+typedef NotificationPayload = ({
+ int scheduleId,
+ DateTime scheduledTime,
+ bool isRepeating,
+ int? notificationId,
+});
+
class NotificationService {
static FlutterLocalNotificationsPlugin Function()? createPlugin =
() => FlutterLocalNotificationsPlugin();
@@ -34,7 +41,8 @@ class NotificationService {
_notificationsPlugin.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>();
- Future initialize() async {
+ Future initialize(
+ {void Function(NotificationPayload tap)? onTap}) async {
if (_initialized) return;
tzdata.initializeTimeZones();
@@ -52,14 +60,66 @@ class NotificationService {
);
await _notificationsPlugin.initialize(
- settings: InitializationSettings(
- android: androidSettings,
- iOS: iosSettings,
- ));
+ settings: InitializationSettings(
+ android: androidSettings,
+ iOS: iosSettings,
+ ),
+ onDidReceiveNotificationResponse: (response) {
+ final payload =
+ _decodePayload(response.payload, notificationId: response.id);
+ if (payload != null) onTap?.call(payload);
+ },
+ );
_initialized = true;
}
+ Future getAppLaunchTap() async {
+ final details =
+ await _notificationsPlugin.getNotificationAppLaunchDetails();
+ if (details == null || !details.didNotificationLaunchApp) return null;
+
+ final response = details.notificationResponse;
+ if (response == null) return null;
+
+ return _decodePayload(response.payload, notificationId: response.id);
+ }
+
+ static const _scheduleIdKey = 'scheduleId';
+ static const _scheduledTimeKey = 'scheduledTime';
+ static const _isRepeatingKey = 'isRepeating';
+
+ String _encodePayload({
+ required int scheduleId,
+ required DateTime scheduledTime,
+ required bool isRepeating,
+ }) =>
+ jsonEncode({
+ _scheduleIdKey: scheduleId,
+ _scheduledTimeKey: scheduledTime.toIso8601String(),
+ if (isRepeating) _isRepeatingKey: true,
+ });
+
+ NotificationPayload? _decodePayload(String? raw, {int? notificationId}) {
+ if (raw == null || raw.isEmpty) return null;
+
+ final decoded = jsonDecode(raw);
+ if (decoded is! Map) return null;
+ final scheduleId = decoded[_scheduleIdKey];
+ final scheduledTime =
+ (decoded[_scheduledTimeKey] as String?)?.toDateTimeOrNull;
+ if (scheduleId is! int || scheduledTime == null) return null;
+
+ return (
+ scheduleId: scheduleId,
+ scheduledTime: scheduledTime,
+ isRepeating: decoded[_isRepeatingKey] == true,
+ notificationId: notificationId,
+ );
+ }
+
+ Future cancel(int id) => _notificationsPlugin.cancel(id: id);
+
NotificationDetails _notificationDetails() {
return const NotificationDetails(
android: AndroidNotificationDetails(
@@ -121,6 +181,7 @@ class NotificationService {
int? id,
String? title,
String? body,
+ String? payload,
}) async {
id ??= Random().nextInt(1 << 31);
@@ -135,17 +196,20 @@ class NotificationService {
title: title,
body: body,
notificationDetails: _notificationDetails(),
+ payload: payload,
);
}
Future scheduleNotification({
required int id,
+ required int scheduleId,
required String title,
required String body,
required DateTime scheduledTime,
}) =>
_schedule(
id: id,
+ scheduleId: scheduleId,
title: title,
body: body,
scheduledTime: scheduledTime,
@@ -153,12 +217,14 @@ class NotificationService {
Future scheduleDailyNotification({
required int id,
+ required int scheduleId,
required String title,
required String body,
required DateTime firstOccurrence,
}) =>
_schedule(
id: id,
+ scheduleId: scheduleId,
title: title,
body: body,
scheduledTime: firstOccurrence,
@@ -167,12 +233,14 @@ class NotificationService {
Future scheduleWeeklyNotification({
required int id,
+ required int scheduleId,
required String title,
required String body,
required DateTime firstOccurrence,
}) =>
_schedule(
id: id,
+ scheduleId: scheduleId,
title: title,
body: body,
scheduledTime: firstOccurrence,
@@ -181,15 +249,17 @@ class NotificationService {
Future _schedule({
required int id,
+ required int scheduleId,
required String title,
required String body,
required DateTime scheduledTime,
DateTimeComponents? matchComponents,
}) async {
- final payload = jsonEncode({
- 'scheduledTime': scheduledTime.toIso8601String(),
- if (matchComponents != null) 'isRepeating': true,
- });
+ final payload = _encodePayload(
+ scheduleId: scheduleId,
+ scheduledTime: scheduledTime,
+ isRepeating: matchComponents != null,
+ );
final dateTime = tz.TZDateTime(
tz.local,
scheduledTime.year,
@@ -229,9 +299,9 @@ class NotificationService {
return pendingNotifications.where((notification) {
final payload = jsonDecode(notification.payload ?? '{}');
- if (payload['isRepeating'] == true) return false;
+ if (payload[_isRepeatingKey] == true) return false;
final scheduledTime =
- (payload['scheduledTime'] as String?)?.toDateTimeOrNull;
+ (payload[_scheduledTimeKey] as String?)?.toDateTimeOrNull;
if (scheduledTime == null) return false;
return scheduledTime.isBefore(clock.now());
}).toList();
@@ -243,6 +313,7 @@ class NotificationService {
await showNotification(
title: notification.title,
body: notification.body,
+ payload: notification.payload,
);
}
}
diff --git a/lib/ui/views/home/settings/application_sites_page.dart b/lib/ui/views/home/settings/application_sites_page.dart
index 318aefd3f..f61e4ed3f 100644
--- a/lib/ui/views/home/settings/application_sites_page.dart
+++ b/lib/ui/views/home/settings/application_sites_page.dart
@@ -6,7 +6,7 @@ import 'package:mona/i18n/helpers/placement_l10n.dart';
import 'package:mona/i18n/translations.g.dart';
import 'package:mona/services/preferences_service.dart';
import 'package:mona/ui/constants/dimensions.dart';
-import 'package:mona/ui/widgets/tinted_switch_tile.dart';
+import 'package:mona/ui/widgets/switch_tile.dart';
import 'package:provider/provider.dart';
class ApplicationSitesPage extends StatelessWidget {
@@ -60,7 +60,7 @@ class ApplicationSitesPage extends StatelessWidget {
return M3ESegmentedColumn(
padding: EdgeInsets.zero,
children: [
- TintedSwitchTile(
+ SwitchTile(
key: const ValueKey('placementScopeToggle'),
title: t.placementSuggestionPerScheduleTitle,
subtitle: t.placementSuggestionPerScheduleDescription,
diff --git a/lib/ui/views/home/settings/language_page.dart b/lib/ui/views/home/settings/language_page.dart
index eaa94f939..47a9e7d2b 100644
--- a/lib/ui/views/home/settings/language_page.dart
+++ b/lib/ui/views/home/settings/language_page.dart
@@ -26,6 +26,8 @@ class LanguagePage extends StatefulWidget {
'fr': (english: 'French', native: 'Français'),
'gl': (english: 'Galician', native: 'Galego'),
'hi': (english: 'Hindi', native: 'हिन्दी'),
+ 'hu': (english: 'Hungarian', native: 'Magyar'),
+ 'id': (english: 'Indonesian', native: 'Bahasa Indonesia'),
'is': (english: 'Icelandic', native: 'Íslenska'),
'it': (english: 'Italian', native: 'Italiano'),
'ko': (english: 'Korean', native: '한국어'),
@@ -120,7 +122,7 @@ class _LanguagePageState extends State {
children: [
TappableListTile(
leading: const Icon(Symbols.translate_rounded),
- title: t.languageMissing,
+ title: t.missingTranslation,
subtitle: t.translateAppDescription,
onTap: () => launchUrl(
Uri.parse(_weblateUrl),
diff --git a/lib/ui/views/home/settings/schedules/edit_schedule/edit_schedule_scheduling_page.dart b/lib/ui/views/home/settings/schedules/edit_schedule/edit_schedule_scheduling_page.dart
index e826a72f6..57a55c24a 100644
--- a/lib/ui/views/home/settings/schedules/edit_schedule/edit_schedule_scheduling_page.dart
+++ b/lib/ui/views/home/settings/schedules/edit_schedule/edit_schedule_scheduling_page.dart
@@ -9,8 +9,8 @@ import 'package:mona/ui/widgets/forms/form_spacer.dart';
import 'package:mona/ui/widgets/forms/form_text_field.dart';
import 'package:mona/ui/widgets/forms/model_form.dart';
import 'package:mona/ui/widgets/scheduling_type_picker.dart';
+import 'package:mona/ui/widgets/switch_tile.dart';
import 'package:mona/ui/widgets/time_list_card.dart';
-import 'package:mona/ui/widgets/tinted_switch_tile.dart';
import 'package:mona/ui/widgets/weekday_picker.dart';
import 'package:mona/util/regex_patterns.dart';
import 'package:mona/util/string_parsing.dart';
@@ -234,7 +234,7 @@ class _EditScheduleSchedulingPageState
padding: EdgeInsets.zero,
margin: EdgeInsets.symmetric(vertical: 8),
children: [
- TintedSwitchTile(
+ SwitchTile(
title: t.anchorToLastIntake,
subtitle: t.anchorToLastIntakeDescription,
value: _anchorToLastIntake,
@@ -264,10 +264,11 @@ class _EditScheduleSchedulingPageState
onEdit: _editTime,
onDelete: _deleteTime,
trailingChildren: [
- TintedSwitchTile(
+ SwitchTile(
title: t.enableNotifications,
subtitle: t.enableNotificationsDescription,
value: _dailyNotify,
+ tintEnabled: false,
onChanged: (value) => setState(() => _dailyNotify = value),
),
],
diff --git a/lib/ui/views/home/settings/schedules/new_schedule_scheduling_page.dart b/lib/ui/views/home/settings/schedules/new_schedule_scheduling_page.dart
index fb8396336..1c930ee18 100644
--- a/lib/ui/views/home/settings/schedules/new_schedule_scheduling_page.dart
+++ b/lib/ui/views/home/settings/schedules/new_schedule_scheduling_page.dart
@@ -15,8 +15,8 @@ import 'package:mona/ui/widgets/forms/form_spacer.dart';
import 'package:mona/ui/widgets/forms/form_text_field.dart';
import 'package:mona/ui/widgets/forms/model_form.dart';
import 'package:mona/ui/widgets/scheduling_type_picker.dart';
+import 'package:mona/ui/widgets/switch_tile.dart';
import 'package:mona/ui/widgets/time_list_card.dart';
-import 'package:mona/ui/widgets/tinted_switch_tile.dart';
import 'package:mona/ui/widgets/weekday_picker.dart';
import 'package:mona/util/regex_patterns.dart';
import 'package:mona/util/string_parsing.dart';
@@ -237,7 +237,7 @@ class _NewScheduleSchedulingPageState extends State {
padding: EdgeInsets.zero,
margin: EdgeInsets.symmetric(vertical: 8),
children: [
- TintedSwitchTile(
+ SwitchTile(
title: t.anchorToLastIntake,
subtitle: t.anchorToLastIntakeDescription,
value: _anchorToLastIntake,
@@ -267,10 +267,11 @@ class _NewScheduleSchedulingPageState extends State {
onEdit: _editTime,
onDelete: _deleteTime,
trailingChildren: [
- TintedSwitchTile(
+ SwitchTile(
title: t.enableNotifications,
subtitle: t.enableNotificationsDescription,
value: _dailyNotify,
+ tintEnabled: false,
onChanged: (value) => setState(() => _dailyNotify = value),
),
],
diff --git a/lib/ui/views/home/settings/secret_settings_page.dart b/lib/ui/views/home/settings/secret_settings_page.dart
index 178d5cd33..0e1c3f79d 100644
--- a/lib/ui/views/home/settings/secret_settings_page.dart
+++ b/lib/ui/views/home/settings/secret_settings_page.dart
@@ -3,7 +3,7 @@ import 'package:m3e_core/m3e_core.dart';
import 'package:mona/i18n/translations.g.dart';
import 'package:mona/services/preferences_service.dart';
import 'package:mona/ui/constants/dimensions.dart';
-import 'package:mona/ui/widgets/tinted_switch_tile.dart';
+import 'package:mona/ui/widgets/switch_tile.dart';
import 'package:provider/provider.dart';
class SecretSettingsPage extends StatelessWidget {
@@ -22,7 +22,7 @@ class SecretSettingsPage extends StatelessWidget {
M3ESegmentedColumn(
padding: EdgeInsets.zero,
children: [
- TintedSwitchTile(
+ SwitchTile(
title: t.slimeMode,
value: preferencesService.slimeModeEnabled,
onChanged: (value) =>
diff --git a/lib/ui/views/home/settings/settings_page.dart b/lib/ui/views/home/settings/settings_page.dart
index 7c28df9bb..47f61dc03 100644
--- a/lib/ui/views/home/settings/settings_page.dart
+++ b/lib/ui/views/home/settings/settings_page.dart
@@ -18,8 +18,8 @@ import 'package:mona/ui/views/home/settings/schedules/schedules_page.dart';
import 'package:mona/ui/views/home/settings/secret_settings_page.dart';
import 'package:mona/ui/views/home/settings/theme_page.dart';
import 'package:mona/ui/views/home/settings/units_page.dart';
+import 'package:mona/ui/widgets/switch_tile.dart';
import 'package:mona/ui/widgets/tappable_list_tile.dart';
-import 'package:mona/ui/widgets/tinted_switch_tile.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
@@ -222,7 +222,7 @@ class _SettingsPageState extends State
));
},
),
- TintedSwitchTile(
+ SwitchTile(
title: t.enableNotifications,
subtitle: t.enableNotificationsDescription,
value: _notificationsEnabled,
@@ -327,7 +327,7 @@ class _SettingsPageState extends State
);
},
),
- TintedSwitchTile(
+ SwitchTile(
title: t.HrtCounter,
subtitle: t.HrtCounterDescription,
value: preferencesService.hrtCounterEnabled,
@@ -342,7 +342,7 @@ class _SettingsPageState extends State
M3ESegmentedColumn(
padding: EdgeInsets.zero,
children: [
- TintedSwitchTile(
+ SwitchTile(
title: t.autoUpdate,
subtitle: t.autoUpdateDescription,
value: _autoCheckUpdatesEnabled,
diff --git a/lib/ui/views/home/take_medication_page.dart b/lib/ui/views/home/take_medication_page.dart
index f7d3dcbb5..ad42bd6d5 100644
--- a/lib/ui/views/home/take_medication_page.dart
+++ b/lib/ui/views/home/take_medication_page.dart
@@ -6,6 +6,7 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:mona/controllers/medication_intake_manager.dart';
import 'package:mona/data/model/administration_route.dart';
import 'package:mona/data/model/generic_supply_item.dart';
+import 'package:mona/data/model/injection_type.dart';
import 'package:mona/data/model/medication_intake.dart';
import 'package:mona/data/model/medication_schedule.dart';
import 'package:mona/data/model/medication_supply_item.dart';
@@ -22,6 +23,7 @@ import 'package:mona/ui/widgets/forms/form_info_text.dart';
import 'package:mona/ui/widgets/forms/form_spacer.dart';
import 'package:mona/ui/widgets/forms/form_text_field.dart';
import 'package:mona/ui/widgets/forms/model_form.dart';
+import 'package:mona/ui/widgets/injection_type_picker.dart';
import 'package:mona/ui/widgets/intake_supply_picker.dart';
import 'package:mona/ui/widgets/placement_picker.dart';
import 'package:mona/util/regex_patterns.dart';
@@ -55,6 +57,8 @@ class _TakeMedicationPageState extends State {
Decimal? _deadSpace;
late TextEditingController _notesController;
bool _isTaken = false;
+ InjectionType? _injectionType;
+ bool _hasInitializedInjectionType = false;
String? get _takenDoseError =>
MedicationIntake.validateDose(_takenDoseController.text);
@@ -80,18 +84,20 @@ class _TakeMedicationPageState extends State {
MedicationIntakeManager(
medicationIntakeProvider, supplyItemProvider, preferencesService)
.takeMedication(
- takenDose: _takenDose,
- scheduledTime: widget.scheduledTime,
- takenDateTime: _takenDate.toUtc(),
- medicationItem: _selectedSupplyItem is MedicationSupplyItem
- ? _selectedSupplyItem as MedicationSupplyItem
- : null,
- genericItems: _selectedGenerics,
- schedule: widget.schedule,
- placements: _selectedPlacements,
- deadSpace: _deadSpace,
- notes: notes,
- wastedAmount: _wastedAmount);
+ takenDose: _takenDose,
+ scheduledTime: widget.scheduledTime,
+ takenDateTime: _takenDate.toUtc(),
+ medicationItem: _selectedSupplyItem is MedicationSupplyItem
+ ? _selectedSupplyItem as MedicationSupplyItem
+ : null,
+ genericItems: _selectedGenerics,
+ schedule: widget.schedule,
+ placements: _selectedPlacements,
+ deadSpace: _deadSpace,
+ notes: notes,
+ wastedAmount: _wastedAmount,
+ injectionType: _injectionType,
+ );
setState(() {
_isTaken = true;
@@ -151,6 +157,12 @@ class _TakeMedicationPageState extends State {
}
}
+ void _onInjectionTypeChanged(InjectionType type) {
+ setState(() {
+ _injectionType = type;
+ });
+ }
+
void _refresh() => setState(() {});
@override
@@ -223,6 +235,14 @@ class _TakeMedicationPageState extends State {
.firstWhere((item) => item?.id == selectedId, orElse: () => null);
}
+ if (!isLoading && !_hasInitializedInjectionType) {
+ final manager = MedicationIntakeManager(
+ medicationIntakeProvider, supplyItemProvider, preferencesService);
+ _injectionType =
+ manager.suggestInjectionType(scheduleId: widget.schedule.id);
+ _hasInitializedInjectionType = true;
+ }
+
return ModelForm(
title: t.takeMedication(scheduleName: widget.schedule.name),
avatar: widget.schedule.administrationRoute.icon,
@@ -254,6 +274,20 @@ class _TakeMedicationPageState extends State {
infoText: supplyItem.localizedSupplyAmount(_takenDose),
),
FormSpacer(),
+ if (usesPlacements && _orderedPlacements.isNotEmpty)
+ PlacementPicker(
+ options: _orderedPlacements,
+ selected: _selectedPlacements,
+ onChanged: _onPlacementChanged,
+ ),
+ if (isInjection)
+ InjectionTypePicker(
+ value: _injectionType ?? InjectionType.intramuscular,
+ onChanged: _onInjectionTypeChanged,
+ ),
+ if (isInjection ||
+ (usesPlacements && _orderedPlacements.isNotEmpty))
+ FormSpacer(),
IntakeSupplyPicker(
medicationItem: _selectedSupplyItem is MedicationSupplyItem
? _selectedSupplyItem as MedicationSupplyItem
@@ -273,14 +307,6 @@ class _TakeMedicationPageState extends State {
() => _selectedGenerics = [..._selectedGenerics, generic]),
),
FormSpacer(),
- if (usesPlacements && _orderedPlacements.isNotEmpty) ...[
- PlacementPicker(
- options: _orderedPlacements,
- selected: _selectedPlacements,
- onChanged: _onPlacementChanged,
- ),
- FormSpacer(),
- ],
if (isInjection) ...[
FormTextField(
controller: _wastedAmountController,
diff --git a/lib/ui/views/intakes/edit_intake_page.dart b/lib/ui/views/intakes/edit_intake_page.dart
index e31dd2fd0..9ec9dd28d 100644
--- a/lib/ui/views/intakes/edit_intake_page.dart
+++ b/lib/ui/views/intakes/edit_intake_page.dart
@@ -5,6 +5,7 @@ import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:mona/controllers/medication_intake_manager.dart';
import 'package:mona/data/model/administration_route.dart';
import 'package:mona/data/model/generic_supply_item.dart';
+import 'package:mona/data/model/injection_type.dart';
import 'package:mona/data/model/medication_intake.dart';
import 'package:mona/data/model/medication_supply_item.dart';
import 'package:mona/data/model/placement.dart';
@@ -21,6 +22,7 @@ import 'package:mona/ui/widgets/forms/form_info_text.dart';
import 'package:mona/ui/widgets/forms/form_spacer.dart';
import 'package:mona/ui/widgets/forms/form_text_field.dart';
import 'package:mona/ui/widgets/forms/model_form.dart';
+import 'package:mona/ui/widgets/injection_type_picker.dart';
import 'package:mona/ui/widgets/intake_supply_picker.dart';
import 'package:mona/ui/widgets/placement_picker.dart';
import 'package:mona/util/regex_patterns.dart';
@@ -51,6 +53,7 @@ class _EditIntakePageState extends State {
List _selectedGenerics = [];
bool _hasInitializedSupplyItem = false;
late TextEditingController _notesController;
+ InjectionType? _injectionType;
String? get _takenDoseError =>
MedicationIntake.validateDose(_takenDoseController.text);
@@ -100,6 +103,7 @@ class _EditIntakePageState extends State {
? _selectedSupplyItem as MedicationSupplyItem
: null,
genericItems: _selectedGenerics,
+ injectionType: _injectionType,
notes: notes,
);
@@ -170,6 +174,12 @@ class _EditIntakePageState extends State {
}
}
+ void _onInjectionTypeChanged(InjectionType type) {
+ setState(() {
+ _injectionType = type;
+ });
+ }
+
void _refresh() => setState(() {});
Future confirmDeleteIntake(BuildContext context) {
@@ -183,6 +193,7 @@ class _EditIntakePageState extends State {
_takenDose = widget.intake.takenDose;
_wastedAmount = widget.intake.wastedAmount ?? Decimal.zero;
_deadSpace = widget.intake.deadSpace ?? Decimal.zero;
+ _injectionType = widget.intake.injectionType;
_takenDoseController = TextEditingController(text: _takenDose.toString());
_wastedAmountController =
TextEditingController(text: _wastedAmount.toString());
@@ -276,6 +287,21 @@ class _EditIntakePageState extends State {
infoText: supplyItem.localizedSupplyAmount(_takenDose),
),
FormSpacer(),
+ if (_usesPlacements && preferencesService.placementsList.isNotEmpty)
+ PlacementPicker(
+ options: preferencesService.placementsList,
+ selected: _selectedPlacements,
+ onChanged: _onPlacementChanged,
+ ),
+ if (_isInjection)
+ InjectionTypePicker(
+ value: _injectionType ?? InjectionType.intramuscular,
+ onChanged: _onInjectionTypeChanged,
+ ),
+ if (_isInjection ||
+ (_usesPlacements &&
+ preferencesService.placementsList.isNotEmpty))
+ FormSpacer(),
IntakeSupplyPicker(
medicationItem: _selectedSupplyItem is MedicationSupplyItem
? _selectedSupplyItem as MedicationSupplyItem
@@ -295,15 +321,6 @@ class _EditIntakePageState extends State {
() => _selectedGenerics = [..._selectedGenerics, generic]),
),
FormSpacer(),
- if (_usesPlacements &&
- preferencesService.placementsList.isNotEmpty) ...[
- PlacementPicker(
- options: preferencesService.placementsList,
- selected: _selectedPlacements,
- onChanged: _onPlacementChanged,
- ),
- FormSpacer(),
- ],
if (_isInjection) ...[
FormTextField(
controller: _wastedAmountController,
diff --git a/lib/ui/views/levels/main_graph_page/chart_date_buttons.dart b/lib/ui/views/levels/main_graph_page/chart_date_buttons.dart
index 8d1309b31..01af3e1d7 100644
--- a/lib/ui/views/levels/main_graph_page/chart_date_buttons.dart
+++ b/lib/ui/views/levels/main_graph_page/chart_date_buttons.dart
@@ -1,9 +1,9 @@
+import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:m3e_core/m3e_core.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:mona/i18n/build_context_extensions.dart';
-import 'package:mona/ui/views/levels/main_graph_page/chart_range_selector.dart';
class ChartDateButtons extends StatelessWidget {
final int index;
@@ -62,17 +62,5 @@ class ChartDateButtons extends StatelessWidget {
}
}
- void _resetToToday() {
- final duration = LevelDuration.values[index];
- final today = DateTime.now();
- final newStartDate = switch (duration) {
- LevelDuration.week => today.subtract(Duration(days: 3)),
- LevelDuration.twoWeeks => today.subtract(Duration(days: 7)),
- LevelDuration.month => today.subtract(Duration(days: 15)),
- LevelDuration.threeMonths => today.subtract(Duration(days: 45)),
- LevelDuration.sixMonths => today.subtract(Duration(days: 90)),
- LevelDuration.year => today.subtract(Duration(days: 180)),
- };
- onStartDateChanged(newStartDate);
- }
+ void _resetToToday() => onStartDateChanged(clock.now());
}
diff --git a/lib/ui/views/levels/main_graph_page/chart_page.dart b/lib/ui/views/levels/main_graph_page/chart_page.dart
index 23dcefaed..c2472989c 100644
--- a/lib/ui/views/levels/main_graph_page/chart_page.dart
+++ b/lib/ui/views/levels/main_graph_page/chart_page.dart
@@ -1,3 +1,4 @@
+import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:mona/data/providers/medication_intake_provider.dart';
@@ -18,13 +19,14 @@ class ChartPage extends StatefulWidget {
}
class _ChartPageState extends State with MinuteTicker {
- double sliderValue = 0;
LevelDuration _duration = LevelDuration.twoWeeks;
- DateTime startDate = DateTime.now().subtract(const Duration(days: 9));
+ DateTime targetDate = clock.now().subtract(const Duration(days: 2));
bool _isPanning = false;
@override
Widget build(BuildContext context) {
+ final dayBoundaries = _dayBoundaries();
+
return LiquidGlassBottomClamp(
child: Scaffold(
appBar: AppBar(title: Text(t.estradiolLevelsTitle)),
@@ -56,10 +58,10 @@ class _ChartPageState extends State with MinuteTicker {
onHorizontalDragUpdate: (details) {
final width = constraints.maxWidth;
if (width <= 0) return;
- final shift = _offset.inMicroseconds *
+ final shift = _duration.days.inMicroseconds *
((details.primaryDelta ?? 0) / width);
setState(() {
- startDate = startDate.subtract(
+ targetDate = targetDate.subtract(
Duration(microseconds: shift.round()),
);
});
@@ -67,8 +69,8 @@ class _ChartPageState extends State with MinuteTicker {
onHorizontalDragEnd: (_) =>
setState(() => _isPanning = false),
child: MainGraph(
- startDate: startDate,
- endDate: startDate.add(_offset),
+ startDate: dayBoundaries.$1,
+ endDate: dayBoundaries.$2,
isPanning: _isPanning,
),
);
@@ -83,9 +85,9 @@ class _ChartPageState extends State with MinuteTicker {
bottom: 24),
child: ChartDateButtons(
index: _duration.index,
- startDate: startDate,
- onStartDateChanged: (newStartDate) => setState(() {
- startDate = newStartDate;
+ startDate: targetDate,
+ onStartDateChanged: (newTargetDate) => setState(() {
+ targetDate = newTargetDate;
}),
),
),
@@ -98,20 +100,19 @@ class _ChartPageState extends State with MinuteTicker {
);
}
- Duration get _offset {
- switch (_duration) {
- case LevelDuration.week:
- return const Duration(days: 7);
- case LevelDuration.twoWeeks:
- return const Duration(days: 14);
- case LevelDuration.month:
- return const Duration(days: 30);
- case LevelDuration.threeMonths:
- return const Duration(days: 90);
- case LevelDuration.sixMonths:
- return const Duration(days: 180);
- case LevelDuration.year:
- return const Duration(days: 365);
- }
+ (DateTime, DateTime) _dayBoundaries() {
+ final (daysBefore, daysAfter) = switch (_duration) {
+ LevelDuration.week => (3, 3),
+ LevelDuration.twoWeeks => (7, 6),
+ LevelDuration.month => (15, 14),
+ LevelDuration.threeMonths => (45, 44),
+ LevelDuration.sixMonths => (90, 89),
+ LevelDuration.year => (182, 182),
+ };
+
+ return (
+ targetDate.subtract(Duration(days: daysBefore)),
+ targetDate.add(Duration(days: daysAfter)),
+ );
}
}
diff --git a/lib/ui/views/levels/main_graph_page/chart_range_selector.dart b/lib/ui/views/levels/main_graph_page/chart_range_selector.dart
index 5fc67d4cd..4961ed47c 100644
--- a/lib/ui/views/levels/main_graph_page/chart_range_selector.dart
+++ b/lib/ui/views/levels/main_graph_page/chart_range_selector.dart
@@ -35,7 +35,18 @@ class ChartRangeSelector extends StatelessWidget {
}
}
-enum LevelDuration { week, twoWeeks, month, threeMonths, sixMonths, year }
+enum LevelDuration {
+ week(Duration(days: 7)),
+ twoWeeks(Duration(days: 14)),
+ month(Duration(days: 30)),
+ threeMonths(Duration(days: 90)),
+ sixMonths(Duration(days: 180)),
+ year(Duration(days: 365));
+
+ const LevelDuration(this.days);
+
+ final Duration days;
+}
extension _DurationLabel on LevelDuration {
String get label => switch (this) {
diff --git a/lib/ui/views/main_tabs.dart b/lib/ui/views/main_tabs.dart
index e9d516534..2bf80a827 100644
--- a/lib/ui/views/main_tabs.dart
+++ b/lib/ui/views/main_tabs.dart
@@ -1,8 +1,9 @@
// main_tabs.dart
import 'package:flutter/material.dart';
-import 'package:material_symbols_icons/material_symbols_icons.dart';
+import 'package:material_symbols_icons/symbols.dart';
import 'package:mona/i18n/translations.g.dart';
import 'package:mona/ui/views/levels/blood_tests_page/blood_test_page.dart';
+import 'package:mona/ui/views/levels/blood_tests_page/new_blood_test_page.dart';
import 'package:mona/ui/views/levels/levels_page/levels_page.dart';
import 'home/home_page.dart';
import 'home/settings/settings_page.dart';
@@ -52,6 +53,7 @@ List getMainTabs(BuildContext context) {
title: t.nav_levels,
page: const LevelsPage(),
icon: Symbols.labs_rounded,
+ navKey: const ValueKey('navTabLevels'),
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
buildActions: (context) => [
IconButton(
@@ -63,6 +65,18 @@ List getMainTabs(BuildContext context) {
},
),
],
+ buildFab: (context) => FloatingActionButton(
+ tooltip: t.addBloodTest,
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ fullscreenDialog: true,
+ builder: (context) => NewBloodTestPage(),
+ ),
+ );
+ },
+ child: const Icon(Symbols.add_rounded),
+ ),
),
MainTabConfig(
title: t.nav_supplies,
diff --git a/lib/ui/widgets/injection_type_picker.dart b/lib/ui/widgets/injection_type_picker.dart
new file mode 100644
index 000000000..0ed43782d
--- /dev/null
+++ b/lib/ui/widgets/injection_type_picker.dart
@@ -0,0 +1,59 @@
+import 'package:flutter/material.dart';
+import 'package:m3e_core/m3e_core.dart';
+import 'package:material_symbols_icons/symbols.dart';
+import 'package:mona/data/model/injection_type.dart';
+import 'package:mona/i18n/helpers/injection_type_l10n.dart';
+import 'package:mona/i18n/translations.g.dart';
+import 'package:mona/ui/widgets/tappable_list_tile.dart';
+
+class InjectionTypePicker extends StatelessWidget {
+ final InjectionType value;
+ final ValueChanged onChanged;
+
+ const InjectionTypePicker({
+ super.key,
+ required this.value,
+ required this.onChanged,
+ });
+
+ Future _openTypeSheet(BuildContext context) async {
+ final selected = await showModalBottomSheet(
+ context: context,
+ showDragHandle: true,
+ isScrollControlled: true,
+ builder: (sheetContext) => SafeArea(
+ child: ListView(
+ shrinkWrap: true,
+ children: [
+ for (final type in InjectionType.values)
+ ListTile(
+ title: Text(type.localizedName),
+ trailing:
+ type == value ? const Icon(Symbols.check_rounded) : null,
+ onTap: () => Navigator.of(sheetContext).pop(type),
+ ),
+ ],
+ ),
+ ),
+ );
+
+ if (selected == null || selected == value) return;
+ onChanged(selected);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return M3ESegmentedColumn(
+ padding: EdgeInsets.zero,
+ margin: EdgeInsets.symmetric(vertical: 8),
+ children: [
+ TappableListTile(
+ title: value.localizedName,
+ subtitle: t.injectionType,
+ trailing: const Icon(Symbols.syringe_rounded),
+ onTap: () => _openTypeSheet(context),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/widgets/intake_supply_picker.dart b/lib/ui/widgets/intake_supply_picker.dart
index c99865db9..d7422655e 100644
--- a/lib/ui/widgets/intake_supply_picker.dart
+++ b/lib/ui/widgets/intake_supply_picker.dart
@@ -126,6 +126,11 @@ class IntakeSupplyPicker extends StatelessWidget {
title: Text(item.name),
subtitle: Text(item.localizedConcentrationAndRemaining),
trailing: IconButton(
+ style: IconButton.styleFrom(
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ minimumSize: Size.zero,
+ padding: EdgeInsets.zero,
+ ),
icon: const Icon(Symbols.close_rounded),
onPressed: onRemoveMedication,
),
@@ -140,6 +145,11 @@ class IntakeSupplyPicker extends StatelessWidget {
title: Text(generic.name),
subtitle: Text(generic.localizedSummary),
trailing: IconButton(
+ style: IconButton.styleFrom(
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ minimumSize: Size.zero,
+ padding: EdgeInsets.zero,
+ ),
icon: const Icon(Symbols.close_rounded),
onPressed: () => onRemoveGenericAt(index),
),
diff --git a/lib/ui/widgets/tinted_switch_tile.dart b/lib/ui/widgets/switch_tile.dart
similarity index 81%
rename from lib/ui/widgets/tinted_switch_tile.dart
rename to lib/ui/widgets/switch_tile.dart
index 4bb23fa92..09a5a51c9 100644
--- a/lib/ui/widgets/tinted_switch_tile.dart
+++ b/lib/ui/widgets/switch_tile.dart
@@ -1,18 +1,20 @@
import 'package:flutter/material.dart';
-class TintedSwitchTile extends StatelessWidget {
- const TintedSwitchTile({
+class SwitchTile extends StatelessWidget {
+ const SwitchTile({
super.key,
required this.title,
required this.value,
required this.onChanged,
this.subtitle,
+ this.tintEnabled = true,
});
final String title;
final String? subtitle;
final bool value;
final ValueChanged onChanged;
+ final bool tintEnabled;
@override
Widget build(BuildContext context) {
@@ -22,7 +24,7 @@ class TintedSwitchTile extends StatelessWidget {
title: Text(title),
subtitle: subtitle == null ? null : Text(subtitle!),
value: value,
- tileColor: value
+ tileColor: value && tintEnabled
? Theme.of(context).colorScheme.primaryContainer.withValues(
alpha: 0.4,
)
diff --git a/pubspec.lock b/pubspec.lock
index 4d2c72eca..a5c67ad39 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -432,7 +432,7 @@ packages:
source: sdk
version: "0.0.0"
flutter_driver:
- dependency: transitive
+ dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index e7a9bf5ed..5926b3009 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -79,6 +79,8 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
+ flutter_driver:
+ sdk: flutter
integration_test:
sdk: flutter
patrol: ^4.6.1
diff --git a/scripts/i18n.sh b/scripts/i18n.sh
index 8ab1ad32b..2b0d68207 100755
--- a/scripts/i18n.sh
+++ b/scripts/i18n.sh
@@ -5,8 +5,8 @@ set -euo pipefail
cd "$(dirname "$0")/.."
-fvm dart run tool/i18next_to_slang.dart
+fvm dart run tool/i18n/i18next_to_slang.dart
fvm dart run slang
-fvm dart run tool/i18next_to_android_widget_res.dart
-fvm dart run tool/sanitize_generated.dart
+fvm dart run tool/i18n/i18next_to_android_widget_res.dart
+fvm dart run tool/i18n/sanitize_generated.dart
fvm dart format lib/i18n/
diff --git a/scripts/screenshots.sh b/scripts/screenshots.sh
new file mode 100755
index 000000000..157885349
--- /dev/null
+++ b/scripts/screenshots.sh
@@ -0,0 +1,156 @@
+#!/usr/bin/env bash
+# Generates in-app store screenshots for each mapped language.
+#
+# Usage:
+# scripts/screenshots.sh [--platform android|ios|all] [--locale ] [--frames-only]
+#
+# --frames-only to skip captures
+# Env overrides:
+# SCREENSHOT_ANDROID_DEVICE adb device id (default: first attached device)
+# SCREENSHOT_IOS_DEVICE simulator name (default: "iPhone 17 Pro Max")
+
+set -euo pipefail
+
+PLATFORM="all"
+ONLY_LOCALE=""
+FRAMES_ONLY=0
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --platform) PLATFORM="$2"; shift 2 ;;
+ --locale) ONLY_LOCALE="$2"; shift 2 ;;
+ --frames-only) FRAMES_ONLY=1; shift ;;
+ *) echo "Unknown argument: $1" >&2; exit 2 ;;
+ esac
+done
+
+MAP="fastlane/screenshot_locales.json"
+TARGET="tool/screenshots/screenshots.dart"
+DRIVER="tool/screenshots/screenshots_driver.dart"
+
+[ -f "$MAP" ] || { echo "Missing locale map: $MAP" >&2; exit 1; }
+jq -e . "$MAP" >/dev/null 2>&1 || { echo "Malformed locale map: $MAP" >&2; exit 1; }
+
+# Read the app-locale tags, optionally filtered to one.
+APP_TAGS=()
+while IFS= read -r tag; do
+ APP_TAGS+=("$tag")
+done < <(jq -r '.[].app' "$MAP")
+
+resolve_ios_udid() {
+ local name="${SCREENSHOT_IOS_DEVICE:-iPhone 17 Pro Max}"
+ local udid
+ udid=$(xcrun simctl list devices available --json \
+ | jq -r --arg n "$name" \
+ '.devices | to_entries[] | .value[] | select(.name==$n) | .udid' \
+ | head -n1) || true
+ [ -n "$udid" ] || { echo "No available iOS simulator named '$name'." >&2; exit 1; }
+ xcrun simctl boot "$udid" >/dev/null 2>&1 || true
+ echo "$udid"
+}
+
+resolve_android_device() {
+ local dev="${SCREENSHOT_ANDROID_DEVICE:-}"
+ if [ -z "$dev" ]; then
+ dev=$(adb devices | awk 'NR>1 && $2=="device"{print $1; exit}')
+ fi
+ [ -n "$dev" ] || { echo "No Android device. Start an emulator first." >&2; exit 1; }
+ echo "$dev"
+}
+
+drive_one() {
+ local platform="$1" app_tag="$2" device="$3"; shift 3
+ local out="build/screenshots/$platform/$app_tag"
+ rm -rf "$out"; mkdir -p "$out"
+ echo "==> $platform / $app_tag"
+
+ local ios_udid=""
+ local android_serial=""
+ if [ "$platform" = "ios" ]; then
+ ios_udid="$device"
+ else
+ android_serial="$device"
+ fi
+
+ SCREENSHOT_OUT="$out" SCREENSHOT_IOS_UDID="$ios_udid" \
+ SCREENSHOT_ANDROID_SERIAL="$android_serial" fvm flutter drive \
+ --driver="$DRIVER" \
+ --target="$TARGET" \
+ -d "$device" \
+ --dart-define=SCREENSHOT_LOCALE="$app_tag" \
+ --dart-define=SCREENSHOT_DEFAULT_COLORS=true \
+ "$@"
+
+ if [ "$platform" = "ios" ]; then
+ xcrun simctl status_bar "$device" clear || true
+ else
+ adb -s "$device" shell am broadcast \
+ -a com.android.systemui.demo -e command exit >/dev/null 2>&1 || true
+ fi
+}
+
+frame_one() {
+ local platform="$1" app_tag="$2" out_dir="$3"
+ local in_dir="build/screenshots/$platform/$app_tag"
+ [ -d "$in_dir" ] || { echo "No raw screenshots in $in_dir" >&2; return 1; }
+ local captions="fastlane/screenshot_captions/$app_tag.json"
+ rm -rf "$out_dir"; mkdir -p "$out_dir"
+ fvm flutter test tool/screenshots/frame_screenshots.dart \
+ --dart-define=FRAME_INPUT_DIR="$in_dir" \
+ --dart-define=FRAME_OUTPUT_DIR="$out_dir" \
+ --dart-define=FRAME_CAPTIONS="$captions"
+ echo "-- framed $platform / $app_tag -> $out_dir"
+}
+
+run_platform() {
+ local platform="$1"
+ local device=""
+ local flavor_args=()
+ if [ "$FRAMES_ONLY" != "1" ]; then
+ if [ "$platform" = "android" ]; then
+ device="$(resolve_android_device)"
+ flavor_args=(--flavor store)
+ else
+ device="$(resolve_ios_udid)"
+ # iOS: default scheme until a `store` scheme exists in Xcode.
+ flavor_args=()
+ fi
+ fi
+
+ for app_tag in ${APP_TAGS[@]+"${APP_TAGS[@]}"}; do
+ [ -n "$ONLY_LOCALE" ] && [ "$app_tag" != "$ONLY_LOCALE" ] && continue
+
+ local store_tag
+ if [ "$platform" = "android" ]; then
+ store_tag=$(jq -r --arg a "$app_tag" '.[] | select(.app==$a) | .play' "$MAP")
+ else
+ store_tag=$(jq -r --arg a "$app_tag" '.[] | select(.app==$a) | .appstore' "$MAP")
+ fi
+
+ if [ "$store_tag" = "null" ] || [ -z "$store_tag" ]; then
+ echo "-- skip $platform / $app_tag (no store locale)"
+ continue
+ fi
+
+ local dest
+ if [ "$platform" = "android" ]; then
+ dest="fastlane/metadata/android/$store_tag/images/phoneScreenshots"
+ else
+ dest="fastlane/screenshots/$store_tag"
+ fi
+
+ if [ "$FRAMES_ONLY" != "1" ]; then
+ drive_one "$platform" "$app_tag" "$device" ${flavor_args[@]+"${flavor_args[@]}"}
+ fi
+ frame_one "$platform" "$app_tag" "$dest"
+ done
+}
+
+case "$PLATFORM" in
+ android) run_platform android ;;
+ ios) run_platform ios ;;
+ all) run_platform android; run_platform ios ;;
+ *) echo "Unknown platform: $PLATFORM" >&2; exit 2 ;;
+esac
+
+echo "Done."
diff --git a/test/controllers/medication_intake_manager_test.dart b/test/controllers/medication_intake_manager_test.dart
index aa928f8e0..da0a9216d 100644
--- a/test/controllers/medication_intake_manager_test.dart
+++ b/test/controllers/medication_intake_manager_test.dart
@@ -8,6 +8,7 @@ import 'package:mona/data/model/administration_route.dart';
import 'package:mona/data/model/dosing_basis.dart';
import 'package:mona/data/model/ester.dart';
import 'package:mona/data/model/generic_supply_item.dart';
+import 'package:mona/data/model/injection_type.dart';
import 'package:mona/data/model/medication_intake.dart';
import 'package:mona/data/model/medication_supply_item.dart';
import 'package:mona/data/model/molecule.dart';
@@ -148,6 +149,7 @@ void main() {
schedule: schedule,
placements: [aPlacement(preset: PlacementPreset.leftThigh)],
notes: notes,
+ injectionType: InjectionType.subcutaneous,
);
});
@@ -212,6 +214,11 @@ void main() {
// Assert
expect(addedIntake.notes, notes);
});
+
+ test('sets injectionType on the intake from the schedule', () {
+ // Assert
+ expect(addedIntake.injectionType, InjectionType.subcutaneous);
+ });
});
group('no supply items', () {
@@ -827,12 +834,14 @@ void main() {
medicationSupplyItemId: null,
wastedAmount: null,
deadSpace: null,
+ injectionType: InjectionType.subcutaneous,
);
final newDose = Decimal.parse('3');
final newWasted = Decimal.parse('0.2');
final newDeadSpace = Decimal.parse('50');
final newTimezone = 'Europe/Paris';
final newNotes = 'edited';
+ final newType = InjectionType.intramuscular;
when(mockMedicationIntakeProvider.updateIntake(any))
.thenAnswer((inv) async {
@@ -850,6 +859,7 @@ void main() {
placements: [aCustomPlacement('belly')],
medicationItem: null,
notes: newNotes,
+ injectionType: newType,
);
// Assert
@@ -866,7 +876,8 @@ void main() {
[aCustomPlacement('belly')])
.having((i) => i.notes, 'notes', newNotes)
.having((i) => i.medicationSupplyItemId, 'medicationSupplyItemId',
- isNull),
+ isNull)
+ .having((i) => i.injectionType, 'injectionType', newType),
);
});
@@ -1349,6 +1360,51 @@ void main() {
});
});
+ group('suggestInjectionType', () {
+ test('returns null when the schedule is not an injection', () {
+ // Arrange
+ when(mockMedicationIntakeProvider.getTakenIntakesDescForSchedule(1234))
+ .thenReturn([
+ aMedicationIntake(administrationRoute: AdministrationRoute.oral)
+ ]);
+
+ // Act
+ final suggestion = manager.suggestInjectionType(scheduleId: 1234);
+
+ // Assert
+ expect(suggestion, isNull);
+ });
+
+ test('returns null when there are no intakes for a schedule', () {
+ // Arrange
+ when(mockMedicationIntakeProvider.getTakenIntakesDescForSchedule(1234))
+ .thenReturn([]);
+
+ // Act
+ final suggestion = manager.suggestInjectionType(scheduleId: 1234);
+
+ // Assert
+ expect(suggestion, isNull);
+ });
+
+ test('returns the injection type of the last intake', () {
+ // Arrange
+ when(mockMedicationIntakeProvider.getLastTakenIntakeForSchedule(1234))
+ .thenReturn(
+ aMedicationIntake(
+ administrationRoute: AdministrationRoute.injection,
+ injectionType: InjectionType.subcutaneous,
+ ),
+ );
+
+ // Act
+ final suggestion = manager.suggestInjectionType(scheduleId: 1234);
+
+ // Assert
+ expect(suggestion, InjectionType.subcutaneous);
+ });
+ });
+
group('suggestMedicationItem', () {
test('suggests the previous intake vial when it still matches', () {
// Arrange
diff --git a/test/controllers/notification_scheduler_test.dart b/test/controllers/notification_scheduler_test.dart
index 96d82b310..2421de739 100644
--- a/test/controllers/notification_scheduler_test.dart
+++ b/test/controllers/notification_scheduler_test.dart
@@ -1,3 +1,5 @@
+import 'dart:convert';
+
import 'package:clock/clock.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -272,6 +274,35 @@ void main() {
});
});
+ group('payload', () {
+ String capturedScheduledPayload() => verify(plugin.zonedSchedule(
+ id: anyNamed('id'),
+ title: anyNamed('title'),
+ body: anyNamed('body'),
+ scheduledDate: anyNamed('scheduledDate'),
+ notificationDetails: anyNamed('notificationDetails'),
+ androidScheduleMode: anyNamed('androidScheduleMode'),
+ matchDateTimeComponents: anyNamed('matchDateTimeComponents'),
+ payload: captureAnyNamed('payload'),
+ )).captured.single as String;
+
+ test('embeds the schedule id in the scheduled payload', () async {
+ // Arrange
+ when(planner.planNotifications(daysAhead: anyNamed('daysAhead')))
+ .thenReturn(
+ [anOccurrencePlan(schedule: aMedicationSchedule(id: 42))]);
+ final sut = NotificationScheduler(planner, preferences);
+
+ // Act
+ await sut.regenerateAll('en');
+
+ // Assert
+ final payload =
+ jsonDecode(capturedScheduledPayload()) as Map;
+ expect(payload['scheduleId'], 42);
+ });
+ });
+
group('notification ids', () {
List capturedScheduledIds() => verify(plugin.zonedSchedule(
id: captureAnyNamed('id'),
diff --git a/test/controllers/slot_finder_test.dart b/test/controllers/slot_finder_test.dart
new file mode 100644
index 000000000..23ddb515b
--- /dev/null
+++ b/test/controllers/slot_finder_test.dart
@@ -0,0 +1,98 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mona/controllers/slot_finder.dart';
+import 'package:mona/data/model/date.dart';
+import 'package:mona/data/model/intake_slot.dart';
+import 'package:mona/data/model/medication_schedule.dart';
+import 'package:mona/data/model/scheduling_strategy.dart';
+
+import '../fixtures.dart';
+
+void main() {
+ DateTime aScheduledTime({TimeOfDay? at}) =>
+ DateTime(2026, 2, 8, at?.hour ?? 0, at?.minute ?? 0);
+
+ IntakeSlot aSlot(
+ MedicationSchedule schedule, {
+ TimeOfDay? time,
+ bool taken = false,
+ }) =>
+ IntakeSlot(
+ schedule: schedule,
+ status: taken ? ScheduleStatus.taken : ScheduleStatus.upcoming,
+ date: Date.today(),
+ time: time,
+ intake: taken ? aMedicationIntake(scheduleId: schedule.id) : null,
+ );
+
+ group('findSlot', () {
+ test('finds the correct target for a non-taken slot', () {
+ // Arrange
+ final schedule = aMedicationSchedule();
+ final slots = [aSlot(schedule)];
+
+ // Act
+ final target = findSlot(schedule.id, aScheduledTime(), slots);
+
+ // Assert
+ expect(target, slots.single);
+ });
+
+ test('returns null when the matching slot is already taken', () {
+ // Arrange
+ final schedule = aMedicationSchedule();
+ final slots = [aSlot(schedule, taken: true)];
+
+ // Act
+ final target = findSlot(schedule.id, aScheduledTime(), slots);
+
+ // Assert
+ expect(target, isNull);
+ });
+
+ test('returns null when no slot matches the schedule id', () {
+ // Arrange
+ final tapped = aMedicationSchedule();
+ final other = aMedicationSchedule();
+ final slots = [aSlot(other)];
+
+ // Act
+ final target = findSlot(tapped.id, aScheduledTime(), slots);
+
+ // Assert
+ expect(target, isNull);
+ });
+
+ test('matches the daily slot whose time of day equals the scheduled time',
+ () {
+ // Arrange
+ final schedule = aMedicationSchedule();
+ final slots = [
+ aSlot(schedule, time: morning),
+ aSlot(schedule, time: afternoon),
+ ];
+
+ // Act
+ final target =
+ findSlot(schedule.id, aScheduledTime(at: afternoon), slots);
+
+ // Assert
+ expect(target, slots.last);
+ });
+
+ test('returns null when no daily slot matches the scheduled time', () {
+ // Arrange
+ final schedule = aMedicationSchedule();
+ final slots = [
+ aSlot(schedule, time: morning),
+ aSlot(schedule, time: afternoon),
+ ];
+
+ // Act
+ final target = findSlot(schedule.id, aScheduledTime(at: evening), slots);
+
+ // Assert
+ expect(target, isNull);
+ });
+ });
+}
diff --git a/test/fixtures.dart b/test/fixtures.dart
index 2532f138d..800d0f330 100644
--- a/test/fixtures.dart
+++ b/test/fixtures.dart
@@ -8,6 +8,7 @@ import 'package:mona/data/model/date.dart';
import 'package:mona/data/model/dosing_basis.dart';
import 'package:mona/data/model/ester.dart';
import 'package:mona/data/model/generic_supply_item.dart';
+import 'package:mona/data/model/injection_type.dart';
import 'package:mona/data/model/medication_intake.dart';
import 'package:mona/data/model/medication_schedule.dart';
import 'package:mona/data/model/medication_supply_item.dart';
@@ -131,6 +132,7 @@ MedicationIntake aMedicationIntake({
Ester? ester,
List placements = const [],
DosingBasis dosingBasis = DosingBasis.mass,
+ InjectionType? injectionType,
}) {
final takenAt = !taken
? null
@@ -154,6 +156,7 @@ MedicationIntake aMedicationIntake({
ester: ester,
placements: placements,
dosingBasis: dosingBasis,
+ injectionType: injectionType,
);
}
diff --git a/test/services/notification_service_test.dart b/test/services/notification_service_test.dart
index 73c30d16d..7ae90b525 100644
--- a/test/services/notification_service_test.dart
+++ b/test/services/notification_service_test.dart
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:clock/clock.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mona/services/notification_service.dart';
@@ -12,6 +13,8 @@ class FakeFlutterLocalNotificationsPlugin
implements FlutterLocalNotificationsPlugin {
final List