Commit 79767a2

derdilla <derdilla06@gmail.com>
2023-08-02 14:18:24
add documentation
1 parent 336fc84
lib/l10n/app_de.arb
@@ -39,7 +39,7 @@
   },
   "errNoFileOpened": "Keine Datei geöffnet",
   "@errNoFileOpened": {},
-  "errNotStarted": "Nicht begonnen'",
+  "errNotStarted": "Nicht begonnen",
   "@errNotStarted": {},
   "errNoValue": "Bitte Wert eingeben",
   "@errNoValue": {},
lib/l10n/app_en.arb
@@ -29,7 +29,7 @@
   "@errDiaGtSys": {},
   "errUnknown": "Unknown error",
   "@errUnknown": {},
-  "errCantOpenURL": "Can't open URL: {url}",
+  "errCantOpenURL": "Can''t open URL: {url}",
   "@errCantOpenURL": {
     "placeholders": {
       "url": {
@@ -53,9 +53,9 @@
   "@errWrongImportFormat": {},
   "errNeedHeadline": "You can only import files with a headline.",
   "@errNeedHeadline": {},
-  "errCantReadFile": "The file's contents can not be read",
+  "errCantReadFile": "The file''s contents can not be read",
   "@errCantReadFile": {},
-  "errNotImportable": "This file can't be imported",
+  "errNotImportable": "This file can''t be imported",
   "@errNotImportable": {},
   "btnCancel": "CANCEL",
   "@btnCancel": {},
@@ -233,7 +233,7 @@
       }
     }
   },
-  "exportWarnConfigNotImportable": "Hey! Just a friendly heads up: the current export configuration won't be importable. To fix it, make sure you set the export type as CSV, enable the headline, and include one of the time formats available.",
+  "exportWarnConfigNotImportable": "Hey! Just a friendly heads up: the current export configuration won''t be importable. To fix it, make sure you set the export type as CSV, enable the headline, and include one of the time formats available.",
   "@exportWarnConfigNotImportable": {},
   "exportWarnNotEveryFieldExported": "Beware that you are not exporting all fields: {fields} {count, plural, one{is} other{are}} missing.",
   "@exportWarnNotEveryFieldExported": {
@@ -286,11 +286,11 @@
   "@warnAboutTxt2": {},
   "warnAboutTxt3": "Feel free to change the values to suit your needs and follow the recommendations of your doctor.",
   "@warnAboutTxt3": {},
-  "enterTimeFormatTxt1": "A formatter string is a blend of predefined ICU/Skeleton strings and any additional text you'd like to include.",
+  "enterTimeFormatTxt1": "A formatter string is a blend of predefined ICU/Skeleton strings and any additional text you''d like to include.",
   "@enterTimeFormatTxt1": {},
-  "enterTimeFormatTxt2": "If you're curious about the complete list of valid formats, you can find them right here.",
+  "enterTimeFormatTxt2": "If you''re curious about the complete list of valid formats, you can find them right here.",
   "@enterTimeFormatTxt2": {},
-  "enterTimeFormatTxt3": "Just a friendly reminder, using longer or shorter format Strings won't magically alter the width of the table columns, which might lead to some awkward line breaks and text not showing.",
+  "enterTimeFormatTxt3": "Just a friendly reminder, using longer or shorter format Strings won''t magically alter the width of the table columns, which might lead to some awkward line breaks and text not showing.",
   "@enterTimeFormatTxt3": {},
   "enterTimeFormatTxt4": "default: \"yy-MM-dd HH:mm\"",
   "@enterTimeFormatTxt4": {},
@@ -373,11 +373,13 @@
   "@pulsePressure": {},
   "unixTimestamp" : "Unix timestamp",
   "@unixTimestamp": {},
-  "errCantEditThis": "You can't edit this. Feel free to look at the values for creating a new entry.",
+  "errCantEditThis": "You can''t edit this. Feel free to look at the values for creating a new entry.",
   "addExportformat": "Add exportformat",
   "@addExportformat": {},
   "edit": "Edit",
   "@edit": {},
   "delete": "Delete",
-  "@delete": {}
+  "@delete": {},
+  "exportFieldFormatDocumentation": "'## Variables\nThe export field format support inserting values for the following placeholders:\n- `$TIMESTAMP:` Represents the time since the Unix epoch in milliseconds.\n- `$SYS:` Provides a value if available; otherwise, it defaults to -1.\n- `$DIA:` Provides a value if available; otherwise, it defaults to -1.\n- `$PUL:` Provides a value if available; otherwise, it defaults to -1.\n- `$NOTE:` Provides a value if available; otherwise, it defaults to -1.\n\nIf any of the placeholders mentioned above are not present in the blood pressure record, they will be replaced with -1.\n\n## Math\nYou can use basic mathematics inside double brackets (\"`{{}}`\").\n\nThe following mathematical operations are supported:\n- Operations: +, -, *, /, %, ^\n- One-parameter functions: abs, acos, asin, atan, ceil, cos, cosh, cot, coth, csc, csch, exp, floor, ln, log, round sec, sech, sin, sinh, sqrt, tan, tanh \n- Two-parameter functions: log, nrt, pow\n- Constants: e, pi, ln2, ln10, log2e, log10e, sqrt1_2, sqrt2\nFor the full math interpreter specification, you can refer to the [function_tree](https://pub.dev/documentation/function_tree/latest#interpreter) specification\n\n## Time\nTo format a timestamp, use the following syntax: `$FORMAT{<timestamp>,<formatString>}`. The `<formatString>` supports the same format as described on the ICU/Skeleton string [documentation page](screen://TimeFormattingHelp).\n\n## Processing order\n1. variable replacement\n2. Math\n3. Date format'",
+  "@exportFieldFormatDocumentation": {}
 }
lib/l10n/app_fr.arb
@@ -25,7 +25,7 @@
   "@btnConfirm": {},
   "sysLong": "Systolique",
   "@sysLong": {},
-  "allowManualTimeInput": "Permettre la saisie manuelle de l'heure",
+  "allowManualTimeInput": "Permettre la saisie manuelle de l''heure",
   "@allowManualTimeInput": {},
   "btnUndo": "ANNULER",
   "@btnUndo": {},
@@ -33,17 +33,17 @@
   "@btnNext": {},
   "pulLong": "Pouls",
   "@pulLong": {},
-  "enterTimeFormatScreen": "Format de l'heure",
+  "enterTimeFormatScreen": "Format de l''heure",
   "@enterTimeFormatScreen": {},
   "theme": "Thème",
   "@theme": {},
   "light": "Clair",
   "@light": {},
-  "iconSize": "Taille de l'icône",
+  "iconSize": "Taille de l''icône",
   "@iconSize": {},
   "graphLineThickness": "Épaisseur de la ligne",
   "@graphLineThickness": {},
-  "animationSpeed": "Durée de l'animation",
+  "animationSpeed": "Durée de l''animation",
   "@animationSpeed": {},
   "behavior": "Comportement",
   "@behavior": {},
@@ -63,7 +63,7 @@
       }
     }
   },
-  "exportFormat": "Format d'exportation",
+  "exportFormat": "Format d''exportation",
   "@exportFormat": {},
   "export": "EXPORTER",
   "@export": {},
@@ -71,7 +71,7 @@
   "@exportCustomEntries": {},
   "textDelimiter": "Délimiteur de texte",
   "@textDelimiter": {},
-  "enterTimeFormatTxt1": "Une chaine de formatage est un mélange de chaines ICU/Skeleton et de n'importe quel texte supplémentaire que vous souhaitez inclure.",
+  "enterTimeFormatTxt1": "Une chaine de formatage est un mélange de chaines ICU/Skeleton et de n''importe quel texte supplémentaire que vous souhaitez inclure.",
   "@enterTimeFormatTxt1": {},
   "enterTimeFormatTxt2": "Si vous souhaitez en savoir plus sur la liste complète des formats valides, vous pouvez trouver ces derniers juste ici.",
   "@enterTimeFormatTxt2": {},
@@ -103,7 +103,7 @@
   "@errDiaGtSys": {},
   "errUnknown": "Erreur inconnue",
   "@errUnknown": {},
-  "errCantOpenURL": "Impossible d'ouvrir l'adresse {url}",
+  "errCantOpenURL": "Impossible d''ouvrir l''adresse {url}",
   "@errCantOpenURL": {
     "placeholders": {
       "url": {
@@ -121,13 +121,13 @@
   "@errNotEnoughDataToGraph": {},
   "errNoData": "pas de données",
   "@errNoData": {},
-  "errNeedHeadline": "Vous ne pouvez importer que des fichiers avec une ligne d'entête.",
+  "errNeedHeadline": "Vous ne pouvez importer que des fichiers avec une ligne d''entête.",
   "@errNeedHeadline": {},
   "errCantReadFile": "Le contenu du fichier ne peut pas être lu",
   "@errCantReadFile": {},
   "errNotImportable": "Ce fichier ne peut pas être importé",
   "@errNotImportable": {},
-  "graphTitlesCount": "Compteur d'étiquettes de graphique",
+  "graphTitlesCount": "Compteur d''étiquettes de graphique",
   "@graphTitlesCount": {},
   "accentColor": "Couleur du thème",
   "@accentColor": {},
@@ -141,11 +141,11 @@
   "@validateInputs": {},
   "age": "Âge",
   "@age": {},
-  "determineWarnValues": "Déterminer les valeurs d'alerte",
+  "determineWarnValues": "Déterminer les valeurs d''alerte",
   "@determineWarnValues": {},
   "aboutWarnValuesScreen": "À propos",
   "@aboutWarnValuesScreen": {},
-  "aboutWarnValuesScreenDesc": "Plus d'informations sur les valeurs d'alerte",
+  "aboutWarnValuesScreenDesc": "Plus d''informations sur les valeurs d''alerte",
   "@aboutWarnValuesScreenDesc": {},
   "sysWarn": "Alerte sur la systole",
   "@sysWarn": {},
@@ -179,7 +179,7 @@
   },
   "sharedPrefsDump": "Contenu des préférences partagées :",
   "@sharedPrefsDump": {},
-  "exportDir": "Répertoire d'export",
+  "exportDir": "Répertoire d''export",
   "@exportDir": {},
   "exportAfterEveryInput": "Exporter après chaque saisie",
   "@exportAfterEveryInput": {},
@@ -195,7 +195,7 @@
   "@exportMimeType": {},
   "exportMimeTypeDesc": "Signaler le type aux autres applications",
   "@exportMimeTypeDesc": {},
-  "exportCsvHeadline": "Ligne d'entête",
+  "exportCsvHeadline": "Ligne d''entête",
   "@exportCsvHeadline": {},
   "exportCsvHeadlineDesc": "Aide pour distinguer les types",
   "@exportCsvHeadlineDesc": {},
@@ -237,9 +237,9 @@
   },
   "statistics": "Statistiques",
   "@statistics": {},
-  "exportWarnConfigNotImportable": "Coucou ! Ceci est juste un avertissement amical : les options d'export choisies ne seront pas importables. Pour corriger cela, choisissez un export en CSV, activez la ligne d'entête et sélectionnez un des formats d'heure disponibles.",
+  "exportWarnConfigNotImportable": "Coucou ! Ceci est juste un avertissement amical : les options d''export choisies ne seront pas importables. Pour corriger cela, choisissez un export en CSV, activez la ligne d''entête et sélectionnez un des formats d''heure disponibles.",
   "@exportWarnConfigNotImportable": {},
-  "exportWarnNotEveryFieldExported": "Attention, vous n'exportez pas tous les champs : {fields} {count, plural, one{est manquant} other{sont manquants}}.",
+  "exportWarnNotEveryFieldExported": "Attention, vous n''exportez pas tous les champs : {fields} {count, plural, one{est manquant} other{sont manquants}}.",
   "@exportWarnNotEveryFieldExported": {
     "placeholders": {
       "count": {
@@ -280,17 +280,17 @@
       }
     }
   },
-  "warnValues": "Valeurs d'alerte",
+  "warnValues": "Valeurs d''alerte",
   "@warnValues": {},
-  "warnAboutTxt1": "Les valeurs d'alerte ne sont que des suggestions et n'ont pas valeur d'avis médical.",
+  "warnAboutTxt1": "Les valeurs d''alerte ne sont que des suggestions et n''ont pas valeur d''avis médical.",
   "@warnAboutTxt1": {},
-  "warnAboutTxt2": "Les valeurs par défaut dépendant de l'âge proviennent de cette source.",
+  "warnAboutTxt2": "Les valeurs par défaut dépendant de l''âge proviennent de cette source.",
   "@warnAboutTxt2": {},
-  "warnAboutTxt3": "N'hésitez pas à modifier les valeurs pour qu'elles vous conviennent et suivent les recommandations de votre médecin.",
+  "warnAboutTxt3": "N''hésitez pas à modifier les valeurs pour qu''elles vous conviennent et suivent les recommandations de votre médecin.",
   "@warnAboutTxt3": {},
   "enterTimeFormatTxt4": "valeur par défaut : \"yy-MM-dd HH:mm\"",
   "@enterTimeFormatTxt4": {},
-  "enterTimeFormatString": "format d'heure",
+  "enterTimeFormatString": "format d''heure",
   "@enterTimeFormatString": {},
   "dateFormatting": "Formatage de date",
   "@dateFormatting": {},
@@ -333,12 +333,12 @@
   "@last30Days": {},
   "allowMissingValues": "Autoriser les valeurs manquantes",
   "@allowMissingValues": {},
-  "enterTimeFormatTxt3": "Rappel amical : utiliser des chaines de formatage plus courtes ou plus longues ne modifieront pas la largeur des colonnes de table par magie, ce qui pourrait générer des retours à la ligne inattendus ou que du texte n'apparaisse pas.",
+  "enterTimeFormatTxt3": "Rappel amical : utiliser des chaines de formatage plus courtes ou plus longues ne modifieront pas la largeur des colonnes de table par magie, ce qui pourrait générer des retours à la ligne inattendus ou que du texte n''apparaisse pas.",
   "@enterTimeFormatTxt3": {},
   "custom": "Personnalisé",
   "@custom": {},
   "language": "Langue",
   "@language": {},
-  "errTimeAfterNow": "L'heure choisie est dans le futur. Nous avons automatiquement repositionné à l'heure courante. Vous pouvez désactiver ce comportement dans les paramètres !",
+  "errTimeAfterNow": "L''heure choisie est dans le futur. Nous avons automatiquement repositionné à l''heure courante. Vous pouvez désactiver ce comportement dans les paramètres !",
   "@errTimeAfterNow": {}
 }
lib/l10n/app_nb.arb
@@ -322,7 +322,7 @@
   "@enterTimeFormatTxt3": {},
   "enterTimeFormatTxt1": "En formateringsstreng er en miks av predefinert ICU/Skeleton -strenger og all annen tekst du vil inkludere.",
   "@enterTimeFormatTxt1": {},
-  "exportWarnConfigNotImportable": "Gjør eksportoppsettet importerbart ved å sette eksporttypen til \\\"CSV\\\", skru på overskrift, inkluder feltene 'diastolic', 'systolic', 'pulse', 'notes', sammen med ett av de tilgjengelige tidsformatene.",
+  "exportWarnConfigNotImportable": "Gjør eksportoppsettet importerbart ved å sette eksporttypen til \\\"CSV\\\", skru på overskrift, inkluder feltene ''diastolic'', ''systolic'', ''pulse'', ''notes'', sammen med ett av de tilgjengelige tidsformatene.",
   "@exportWarnConfigNotImportable": {},
   "allowMissingValues": "Tillat manglende verdier",
   "@allowMissingValues": {},
lib/screens/subsettings/export_column_data.dart
@@ -1,5 +1,6 @@
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/export_options.dart';
+import 'package:blood_pressure_app/screens/subsettings/export_field_format_documentation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 
@@ -61,7 +62,7 @@ class _EditExportColumnPageState extends State<EditExportColumnPage> {
                           TextFormField(
                             key: const Key('displayName'),
                             initialValue: _displayName,
-                            decoration: InputDecoration(hintText: localizations.displayTitle),
+                            decoration: InputDecoration(label: Text(localizations.displayTitle)),
                             onChanged: (String? value) {
                               if (value != null && value.isNotEmpty) {
                                 setState(() {
@@ -82,7 +83,7 @@ class _EditExportColumnPageState extends State<EditExportColumnPage> {
                           TextFormField(
                             key: Key('internalName$_internalNameKeyNr'), // it should update when display name is changed without unfocussing on edit
                             initialValue: _internalName,
-                            decoration: InputDecoration(hintText: localizations.internalName),
+                            decoration: InputDecoration(label: Text(localizations.internalName)),
                             enabled: (widget.initialInternalName == null),
                             validator: (String? value) {
                               if (value == null || value.isEmpty || RegExp(r'[^A-Za-z0-9]').hasMatch(value)) {
@@ -102,12 +103,21 @@ class _EditExportColumnPageState extends State<EditExportColumnPage> {
                           TextFormField( // TODO: documentation
                             key: const Key('formatPattern'),
                             initialValue: _formatPattern,
-                            decoration: InputDecoration(hintText: localizations.fieldFormat),
+                            decoration: InputDecoration(
+                              label: Text(localizations.fieldFormat),
+                              suffixIcon: IconButton(
+                                  onPressed: () {
+                                    Navigator.of(context).push(MaterialPageRoute(
+                                        builder: (context) => InformationScreen(text: localizations.exportFieldFormatDocumentation)));
+                                  },
+                                  icon: const Icon(Icons.info_outline)
+                              ),
+                            ),
                             maxLines: 6,
                             minLines: 1,
                             validator: (String? value) {
                               if (value == null || value.isEmpty) {
-                                return AppLocalizations.of(context)!.errNoValue;
+                                return localizations.errNoValue;
                               } else if (_internalName != null && _displayName != null) {
                                 try {
                                   final column = ExportColumn(internalName: _internalName!, columnTitle: _displayName!, formatPattern: value);
@@ -143,13 +153,13 @@ class _EditExportColumnPageState extends State<EditExportColumnPage> {
                             Navigator.of(context).pop();
                           },
 
-                          child: Text(AppLocalizations.of(context)!.btnCancel)
+                          child: Text(localizations.btnCancel)
                       ),
                       const Spacer(),
                       FilledButton.icon(
                         key: const Key('btnSave'),
                         icon: const Icon(Icons.save),
-                        label: Text(AppLocalizations.of(context)!.btnSave),
+                        label: Text(localizations.btnSave),
                         onPressed: (widget.editable) ? (() async {
                           if (_formKey.currentState?.validate() ?? false) {
                             widget.onValidSubmit(ExportColumn(
lib/screens/subsettings/export_field_format_documentation.dart
@@ -0,0 +1,45 @@
+import 'package:blood_pressure_app/screens/subsettings/time_formats_explainer.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_markdown/flutter_markdown.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class InformationScreen extends StatelessWidget {
+  /// text in markdown format
+  final String text;
+  const InformationScreen({super.key, required this.text});
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+        appBar: AppBar(
+          backgroundColor: Theme.of(context).primaryColor,
+        ),
+        body: Container(
+          padding: const EdgeInsets.all(10),
+          child: Markdown(
+            selectable: true,
+              onTapLink: (text, destination, title) async {
+                if (destination == null) {
+                  return;
+                }
+                if (destination.startsWith('http://') || destination.startsWith('https://')) {
+                  final url = Uri.tryParse(destination);
+                  if (url != null && await canLaunchUrl(url)) {
+                    launchUrl(url);
+                    return;
+                  }
+                } else if (destination.startsWith('screen://')) {
+                  switch (destination.split('//')[1]) {
+                    case 'TimeFormattingHelp':
+                      Navigator.of(context).push(MaterialPageRoute(builder: (context) => const TimeFormattingHelp()));
+                      return;
+                  }
+                }
+                assert(false, 'Markdown contains invalid URL');
+              },
+            data: text
+          ),
+        )
+    );
+  }
+}
\ No newline at end of file
lib/screens/subsettings/time_formats_explainer.dart
@@ -1,5 +1,4 @@
 import 'package:flutter/material.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 
 class TimeFormattingHelp extends StatelessWidget {
   const TimeFormattingHelp({super.key});
@@ -46,7 +45,6 @@ class TimeFormattingHelp extends StatelessWidget {
   Widget build(BuildContext context) {
     return Scaffold(
         appBar: AppBar(
-          title: Text(AppLocalizations.of(context)!.dateFormatting),
           backgroundColor: Theme.of(context).primaryColor,
         ),
         body: Container(
l10n.yaml
@@ -1,4 +1,5 @@
 arb-dir: lib/l10n
 template-arb-file: app_en.arb
 output-localization-file: app_localizations.dart
-untranslated-messages-file: l10n_errors.txt
\ No newline at end of file
+untranslated-messages-file: l10n_errors.txt
+use-escaping: true
\ No newline at end of file
pubspec.lock
@@ -251,6 +251,14 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_markdown:
+    dependency: "direct main"
+    description:
+      name: flutter_markdown
+      sha256: "4b1bfbb802d76320a1a46d9ce984106135093efd9d969765d07c2125af107bdf"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.6.17"
   flutter_material_color_picker:
     dependency: "direct main"
     description:
@@ -357,6 +365,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.2.0"
+  markdown:
+    dependency: transitive
+    description:
+      name: markdown
+      sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.1.1"
   matcher:
     dependency: transitive
     description:
pubspec.yaml
@@ -32,6 +32,7 @@ dependencies:
   jsaver: ^1.2.0
   function_tree: ^0.8.13
   badges: ^3.1.1
+  flutter_markdown: ^0.6.17
 
 dev_dependencies:
   flutter_test: