From 88adbc68650547343556aa452e16ccf69ff40496 Mon Sep 17 00:00:00 2001 From: DEMEY Fanny Date: Mon, 5 Feb 2024 16:08:18 +0100 Subject: [PATCH] add practices --- README.md | 38 ++++++++--------------- chapters/MBP_005_fr.md | 2 +- chapters/MBP_006_fr.md | 21 +++++++------ chapters/MBP_006bis_fr.md | 6 ++-- chapters/MBP_009_fr.md | 2 +- chapters/MBP_010_fr.md | 24 +++++++-------- chapters/MBP_011_fr.md | 39 ++++++++++++++++++++++++ chapters/MBP_012_fr.md | 54 +++++++++++++++++++++++++++++++++ chapters/demo-image-format.png | Bin 0 -> 2516 bytes 9 files changed, 134 insertions(+), 52 deletions(-) create mode 100644 chapters/MBP_011_fr.md create mode 100644 chapters/MBP_012_fr.md create mode 100644 chapters/demo-image-format.png diff --git a/README.md b/README.md index cb52450..a72a5ce 100644 --- a/README.md +++ b/README.md @@ -30,36 +30,24 @@ UI/UX : * * [Stocker localement les données de configuration non liées aux données du serveur](chapters/MBP_007_fr.md) // TODO à discuter car côté mobile, beaucoup des données statiques sont déjà sur l'appareil. -* [Déployer un Android App Bundle (AAB) plutôt qu'un APK](chapters/MBP_008_fr.md) +* [Déployer un Android App Bundle (AAB) plutôt qu'un APK](chapters/MBP_008_fr.md) // TODO a reformuler * [Supprimer les ressources non utilisées](chapters/MBP_009_fr.md) -* [Minifier et obfusquer le code](chapters/MBP_010_fr.md) +* [Minifier le code](chapters/MBP_010_fr.md) +* [Ne pas redimensionner les images coté application](chapters/MBP_011_fr.md) +* [Éviter d'utiliser des images matricielles](chapters/MBP_012_fr.md) - -// TODO SavedStateHandle +// TODO +// MAIN/IO thread ? // Repository (stratégie offline) +// Eviter image non vectoriel (/chapters/BP_023_fr.md) +// Etudier si l'usage de style/theme plutôt que paramètre individuel est plus efficace (BP_024_fr BP_025_fr) +// Utiliser les paramètres de style abrégés // [Utiliser les notations CSS abrégées](/chapters/BP_026_fr.md) +// Utiliser glide ou équivalent pour mettre en cache les images et éviter de les recharger +// Notifications +// ABI ?? +// Feature play store // TODO pas encore fait la suite -* [Proposer un traitement asynchrone lorsque c'est possible](/chapters/BP_008_fr.md) // TODO à voir si équivalent. - - -* [Choisir les technologies les plus adaptées](/chapters/BP_015_fr.md) -* [Utiliser certains forks applicatifs orientés "performance"](/chapters/BP_016_fr.md) -* [Choisir un format de données adapté](/chapters/BP_017_fr.md) -* [Limiter le nombre de domaine servant les ressources](/chapters/BP_018_fr.md) -* [Remplacer les boutons officiels de partage des réseaux sociaux](/chapters/BP_019_fr.md) -* [Découper les CSS](/chapters/BP_021_fr.md) -* [Limiter le nombre de CSS](/chapters/BP_022_fr.md) -* [Préférer les CSS aux images](/chapters/BP_023_fr.md) -* [Écrire des sélecteurs CSS efficaces](/chapters/BP_024_fr.md) -* [Grouper les déclarations CSS similaires](/chapters/BP_025_fr.md) -* [Utiliser les notations CSS abrégées](/chapters/BP_026_fr.md) -* [Fournir une CSS print](/chapters/BP_027_fr.md) -* [Favoriser les polices standards](/chapters/BP_029_fr.md) -* [Préférer les glyphs aux images](/chapters/BP_030_fr.md) -* [Valider les pages auprès du W3C](/chapters/BP_031_fr.md) -* [Externaliser les CSS et JavaScript](/chapters/BP_032_fr.md) -* [Ne pas redimensionner les images coté navigateur](/chapters/BP_034_fr.md) -* [Éviter d'utiliser des images matricielles pour l'interface](/chapters/BP_035_fr.md) * [Optimiser les images vectorielles](/chapters/BP_036_fr.md) * [Utiliser le chargement paresseux](/chapters/BP_037_fr.md) * [Utiliser le rechargement partiel d'une zone de contenu](/chapters/BP_038_fr.md) diff --git a/chapters/MBP_005_fr.md b/chapters/MBP_005_fr.md index b0b5e8e..3934c50 100644 --- a/chapters/MBP_005_fr.md +++ b/chapters/MBP_005_fr.md @@ -33,7 +33,7 @@ Tout design d'interface doit être réfléchi en amont, en prenant en compte : ### Exemple -// TODO je ne comprends pas bien l'exemple. Il manque une illustration ?. A reformuler +// TODO je ne comprends pas bien l'exemple. diff --git a/chapters/MBP_006_fr.md b/chapters/MBP_006_fr.md index 787e43d..6b22b6d 100644 --- a/chapters/MBP_006_fr.md +++ b/chapters/MBP_006_fr.md @@ -20,22 +20,23 @@ ### Description -Lorsqu'une personne navigue d'une vue à une autre au sein d'une application, mais également entre plusieurs applications sur son mobile, ces vues transitionnent entre différents états. Selon ces changements d'état, pour optimiser l'usage de la mémoire du téléphone, le système va effectuer des opérations telles que : libérer partiellement la mémoire si l'application passe en arrière-plan, supprimer complétement les données de la mémoire si l'utilisateur quitte la vue en cours en appuyant par exemple sur le bouton retour. Ces changements d'état sont matérialisés par le cycle de vie. +Lorsqu'une personne navigue d'un écran à un autre au sein d'une application, mais également entre plusieurs applications sur son mobile, ces applications transitionnent entre différents états. Selon ces changements d'état, pour optimiser l'usage de la mémoire du téléphone, le système va effectuer des opérations telles que : libérer partiellement la mémoire si l'application passe en arrière-plan, supprimer les données en mémoire de l'écran en cours si l'utilisateur quitte celui-ci en appuyant par exemple sur le bouton retour. -Il convient d'optimiser l'accès aux données utilisées faites par la vue, afin d'éviter d'effectuer des requêtes en base de données locale ou sur le réseau à chaque changement d'état. + +Il convient d'optimiser l'accès aux données faites depuis une activité selon son cycle de vie, afin d'éviter d'effectuer inutilement des requêtes en base de données locale ou sur le réseau. Il existe plusieurs bonnes pratiques à appliquer à différents niveaux : - À l'échelle de la vue : - - En utilisant correctement les callbacks du cycle de vie fournies par les composants `Activity` ou `Fragment`. + - En utilisant correctement les callbacks du cycle de vie fournies au sein d'une `Activity`. - À l'échelle du `ViewModel` : - - En utilisant une classe de type `ViewModel` (par exemple via l'implémentation fournie par l'API **Jetpack ViewModel**) ayant un cycle de vie plus long. + - En utilisant une classe de type `ViewModel` (par exemple via l'implémentation fournie par l'API **Jetpack ViewModel**) avec un cycle de vie plus long que celui de la vue. #### A l'échelle de la vue : -Au sein d'une classe de type `Activity` ou `Fragment`, il est courant de surcharger une ou plusieurs méthodes du cycle de vie afin d'initialiser et d'adapter l'état de la vue (via les méthodes `onCreate`, `onResume`, `onPause`,`onStop`, etc.). Il est important d'éviter de récupérer inutilement les données depuis la base de données locale ou le réseau à chaque fois que la vue redevient visible et utilisable (état `RESUMED`), c'est-à-dire au sein de la fonction `onResume`. +Au sein d'une classe de type `Activity`, il est courant de surcharger une ou plusieurs méthodes du cycle de vie afin d'initialiser et d'adapter l'état de la vue (via les méthodes `onCreate`, `onResume`, `onPause`,`onStop`, etc.). Il est important d'éviter de récupérer inutilement les données depuis la base de données locale ou le réseau à chaque fois que la vue redevient visible et utilisable (état `RESUMED`), c'est-à-dire au sein de la fonction `onResume`. Il convient plutôt d'initialiser l'état de la vue au sein de la fonction `onCreate` et de récupérer les données dont elle a besoin soit dans l'état `CREATED`, soit dans l'état `STARTED`. -Note : Dans certaines situations, il est conseillé de développer ses propres composants "lifecycle aware" afin d'être notifié de la même façon de ces changements d'états, afin de rendre le code plus maintenable. +Note : Dans certaines situations, il est conseillé de développer ses propres composants "lifecycle aware" afin d'être notifié de la même façon de ces changements d'états et de rendre le code plus maintenable. #### A l'échelle du `ViewModel` @@ -93,8 +94,8 @@ sealed interface ProfileUIState { ### Principe de validation -| Le nombre ... | est inférieur ou égal à | -|--------------------------------------------------------------------------------------------------|:-----------------------:| +| Le nombre ... | est inférieur ou égal à | +|----------------------------------------------------------------------------------------------------|:-----------------------:| | d'écran effectuant des chargements de données à chaque fois que la vue passe dans l'état `RESUMED` | 0% | -| d'écran rechargeant l'ensemble des données lors d'un changement de configuration | 0% | -| d'écran n'utilisant pas de ViewModel ou équivalent pour maintenir l'état de la vue | 0% | \ No newline at end of file +| d'écran rechargeant l'ensemble des données lors d'un changement de configuration | 0% | +| d'écran n'utilisant pas de ViewModel ou équivalent pour maintenir l'état de la vue plus longtemps | 0% | \ No newline at end of file diff --git a/chapters/MBP_006bis_fr.md b/chapters/MBP_006bis_fr.md index 468afa7..89c7282 100644 --- a/chapters/MBP_006bis_fr.md +++ b/chapters/MBP_006bis_fr.md @@ -29,14 +29,14 @@ Lorsqu'une personne navigue entre plusieurs applications sur son mobile, ces app Au sein de ses deux callbacks, une bonne pratique est de libérer ou d'ajuster les ressources consommatrices initialisées précédemment. Quelques exemples de traitements consommateurs : -* L'affichage d'une animation +* L'affichage d'une animation. * La lecture d'une vidéo ou d'un son. * L'usage des capteurs du téléphone : le GPS, le gyroscope. * L'usage de protocol de connectivité : Bluetooth, Wifi, NFC, etc. ### Exemple -* Soit une application utilisant la position GPS du téléphone. Lorsque l'activité n'est plus visible, il faut désactiver la mise à jour de la position dans la fonction `onStop` ou si cela n'est pas possible, changer la granularité de mise à jour de la position de "fine" à "grossière". -* Soit une activité `PhotoActivity` qui utilise l'appareil photo. Lorsqu'une autre activité prend le focus sur celle-ci par exemple sous forme de modale, mais que la première activité restent visible dessous (dans l'état `PAUSED`), il faut appeler la fonction `camera?.release()` dans la callback `onPause`. +* Soit une application utilisant la position GPS du téléphone. Lorsque l'activité n'est plus visible, il faut désactiver la mise à jour de la position dans la fonction `onStop`. Si cela n'est pas possible, une alternative est de changer la granularité de mise à jour de la position de "fine" à "grossière". +* Soit une activité `PhotoActivity` qui utilise l'appareil photo. Lorsqu'une autre activité prend le focus sur celle-ci par exemple sous forme de modale, mais que la première activité restent visible dessous (elle est donc dans l'état `PAUSED`), il faut appeler la fonction `camera?.release()` dans la callback `onPause`. ### Principe de validation diff --git a/chapters/MBP_009_fr.md b/chapters/MBP_009_fr.md index 630ed39..880dfa6 100644 --- a/chapters/MBP_009_fr.md +++ b/chapters/MBP_009_fr.md @@ -25,7 +25,7 @@ Supprimer les ressources et les classes non utilisées permet de réduire la taille de l'application. Il existe plusieurs moyens d'appliquer cela : -- Utiliser le linter inclus dans Android Studio qui détecte les ressources (`res/`) non utilisées et les supprimer manuellement. +- Utiliser le linter inclus à Android Studio qui détecte les ressources (`res/`) non utilisées et les supprimer manuellement. ```shell res/layout/item_row.xml: Warning: The resource R.layout.item_row appears diff --git a/chapters/MBP_010_fr.md b/chapters/MBP_010_fr.md index c2aebda..9d2cdf0 100644 --- a/chapters/MBP_010_fr.md +++ b/chapters/MBP_010_fr.md @@ -1,4 +1,4 @@ -## Minifier et optimiser le code +## Minifier le code ### Identifiants @@ -22,16 +22,16 @@ ### Description -Utiliser un outil de minification et d'optimisation de code permet de réduire considérable la taille de l'application. -Sur android, cet outil se décompose en quatre processus : --La minification, qui consiste à supprimer les espaces inutiles, les commentaires, les sauts de ligne, les délimiteurs de blocs et ainsi réduire la taille de l'application finale. -- La suppression des ressources non utilisées dans notre code mais également dans les bibliothèques tierces incluses dans notre projet, par exemple si notre code n'utilise qu'une petite partie de celles-ci. -- L'obfuscation. Ce processus réduit le nom des classes et de ses membres, augmentant la sécurité et réduisant par la même occasion la taille des fichiers. Par exemple la classe : `com.monpackage.MaClass` devient une fois compilée : `a.a.B` -- L'optimisation. Ce processus inspecte et optimize le code pour supprimer le code qui n'est jamais exécutée. Par exemple il peut détecter que la branche `else` d'une condition n'est jamais empruntée, et donc la supprimer du code compilé. - -Pour activer et configurer ces processus, il faut activer les paramètres `isShrinkResources` et `isMinfyEnabled` dans le fichier Gradle de l'application, dans les paramètres du build qui sera déployé. Au-delà de ses paramètres, il faut ensuite spécifier les processus qui doivent s'appliquer et sur quelles classes dans le fichier `proguard-rules.pro` du projet. +Utiliser un outil de minification de code permet de réduire considérable la taille d'une application. +Sur Android, cet outil se décompose en quatre processus : +1. La suppression du code non utilisée (appelé aussi "Code shrinking"). Ce processus supprime en particulier le code non utilisé au sein des bibliothèques tierces incluses dans notre projet, par exemple si notre code n'utilise qu'une petite partie de celles-ci. (paramètre `isMinifyEnabled`). +2. L'optimisation. Ce processus inspecte et optimize le code pour supprimer plus en détail les parties du code qui ne sont jamais exécutés. Par exemple il peut détecter que la branche `else` d'une condition n'est jamais empruntée, et donc la supprimer du code compilé. +3. L'obfuscation. Ce processus réduit le nom des classes et de ses membres, augmentant la sécurité et réduisant par la même occasion la taille des fichiers. Par exemple la classe : `com.monpackage.MaClass` devient une fois compilée : `a.a.B`. (paramètre `isMinifyEnabled`). +4. La suppression des ressources non utilisées dans notre code️ appelé aussi "Ressource shrinking" (paramètre `isShrinkResources`). Ce processus ne s'applique que si le paramètre `isMinifyEnabled` est activé. +Pour activer et configurer ces processus, il faut activer les paramètres `isShrinkResources` et `isMinfyEnabled` dans le fichier Gradle de l'application, dans les paramètres du build qui sera déployé. Au-delà de ses paramètres, il faut ensuite spécifier les processus doivent s'appliquer et sur quelles classes dans le fichier `proguard-rules.pro` du projet. +Cette pratique permet généralement de réduire la taille finale de l'application de moitié. ### Exemple @@ -59,6 +59,6 @@ android { ### Principe de validation -| Le nombre ... | est inférieur ou égal à | -|------------------------------------|:-----------------------:| -| de fichiers non minifiés/optimisés | 25 | +| Le nombre ... | est inférieur ou égal à | +|-----------------------------------|:-----------------------:| +| de fichiers non minifiés | 25 | diff --git a/chapters/MBP_011_fr.md b/chapters/MBP_011_fr.md new file mode 100644 index 0000000..8ef2065 --- /dev/null +++ b/chapters/MBP_011_fr.md @@ -0,0 +1,39 @@ +## Ne pas redimensionner les images coté application + +### Identifiants + +| GreenIT | V2 | V3 | V4 | +|:-------:|:----:|:----:|:----:| +| 93 | 20 | 34 | | + +### Catégories + +| Cycle de vie | Tiers | Responsable | +|:---------:|:----:|:----:| +| 3. Réalisation (fabrication / développement) | Utilisateur/Terminal | Utilisateur | + +### Indications + +| Degré de priorité | Mise en oeuvre | Impact écologique | +|:-------------------:|:-------------------------:|:---------------------:| +| 4 | 4 | 4 | + +|Ressources Economisées | +|:----------------------------------------------------------:| +| Processeur / Réseau | + +### Description + +Ne pas redimensionner les images côté application. Cette approche impose en effet de transférer ces images dans leur taille originale, gaspillant ainsi de la bande passante et des cycles CPU. + +### Exemple + +Une image de 350 × 300 pixels encodée en PNG 24 pèse 41 Ko. Redimensionnée dans le code, la même image affichée en vignette à 70 × 60 pixels pèse toujours 41 Ko, alors qu’elle ne devrait pas dépasser 3 Ko ! Soit 38 Ko téléchargés à chaque fois pour rien… + +La meilleure solution consiste à ce que le serveur génére des images à la taille à laquelle elles sont affichées. + +### Principe de validation + +| Le nombre ... | est inférieur ou égal à | +|---------------------------------------------|:-------------------------:| +| d'images redimensionnées dans l'application | 0 | diff --git a/chapters/MBP_012_fr.md b/chapters/MBP_012_fr.md new file mode 100644 index 0000000..77d82f2 --- /dev/null +++ b/chapters/MBP_012_fr.md @@ -0,0 +1,54 @@ +## Eviter d'utiliser des images matricielles + +### Identifiants + +// TODO + +### Catégories + +| Cycle de vie | Tiers | Responsable | +|:---------:|:----:|:----:| +| 5. Utilisation | Utilisateur/Terminal | Utilisateur | + +### Indications + +| Degré de priorité | Mise en oeuvre | Impact écologique | +|:-------------------:|:-------------------------:|:---------------------:| +| 4 | 4 | 4 | + +|Ressources Economisées | +|:----------------------------------------------------------:| +| Réseau | + +### Description + +Choisir le bon format d’image est crucial pour réduire la taille de l'application. +Par ailleurs, avec la multiplication des terminaux, des tailles d’écran et l’augmentation de leur résolution, une approche vectorielle +doit être privilégiée par rapport à des images matricielles. +Grâce à cette bonne pratique, l’interface est indépendante de la résolution de l’écran. On limite donc aussi la dette technique. +La première règle consiste à remplacer les images matricielles (GIF, PNG, JPEG, WebP, etc.) par des images vectoriels (SVG), ou des icônes inclues dans la police de caractères. +S’il n’est pas possible d’utiliser ce format, il convient de convertir a minima les images de type GIF, PNG et JPEG en image de type WebP. En effet, ce dernier offre une compression meilleure et supporte également la gestion de transparence depuis Android 4.3 (API Level 18). + +Android Studio permet de transformer rapidement des images de type PNG ou JPEG en format WebP. Il suffit de faire clic droit sur le fichier de l'image ou un répertoire contenant plusieurs images, et sélectionner "Convert to WebP". + +Note : Il est intéressant de noter qu'un fichier SVG n'est pas inclus tel quel au sein d'un projet Android, mais qu'il est transformé en un fichier XML interprétable par le SDK Android via l'outil d'import d'Android Studio. Ce dernier va optimiser le contenu de ce fichier XML en supprimant les attributs inutiles et les commentaires qui pouvaient être inclus dans le fichier SVG. + +### Exemple + + +![Différentes formes géométriques qui se superposent.](demo-image-format.png) + +Cette image de 198 × 198 pixels pèse : + - 118 Ko dans un format matriciel non compressé ; + - 6,5 Ko en JPEG (compression à 90 %) ; + - 3,8 Ko en PNG ; + - 0,7 Ko en WebP (qualité d'encodage à 75%); + - 0,7 Ko en SVG. + +Le format vectoriel est, dans ce cas précis, 5 à 10 fois moins lourd qu’un format matriciel tout en pouvant être redimensionné à l’infini. + +### Principe de validation + +| Le nombre ... | est inférieur ou égal à | +|-------------------|:-----------------------:| +| d'images matricielles | 5 | diff --git a/chapters/demo-image-format.png b/chapters/demo-image-format.png new file mode 100644 index 0000000000000000000000000000000000000000..7de3dd2b92db057abdc2aa2331fff0ee7cedeb96 GIT binary patch literal 2516 zcmV;_2`l!AP)&1G2TO|fibmM)k zB`)r+hzG|;gQ69$O$iziEV*~tgS47N%Gr?zX=NA81nh#Dh+Qxfu^GXhJn6-wM_p)a zY{Z?K8r0R+qQ0&UckbLlQ&Tg#dwS5<*DpRc;*7XpPo6x%#fulQf8Rc&r)Oa0@|B2O z8VN@SNBreaA7b__C-`{#z5hoeMq$ktYmoJiY#hzc z$E{mcc=__B_-m}jhzQou(15*r_98JM5dr=I(5SV@$;w7S!EtQdmGG&LAZtmgP!;PoN74KA zsaOPQtd#^W2|;&vw?TeGLnA!gJ@Ax0^O$vCdQan;KJ*N=to7^G8{`wUZ0w^}>+9o- z4iQMYoP@4`O79?@h#W#tW4&XvuGk3in~X&S zD=a*L`D}px@yGv)rU67oM56F`p_t7HVuN={NvW8P;^Jb2u?X6x$s`- z%>;ZihAP2q!LDDw&Zqs_+B(rRfQpI=HlqcJ*_?u71&9cb5VLiJslxa_#u!1Y&Wegk zF?#?zckY7UB0s!%@j^7o#w@{Jy?Vv}*yDq=7?JF(9Bj?qCT63*zn}g0y;#?}A)0iM z*wjLvJ$oja_>4+*%ONf{j*o3hF-x#>W#{++FBgqk!^bI+ueN=K<)1GXvj?y&bs4s^ z2}9)4rAyGV$wm&<)z$Fw^fD^E9Ly3deSJE1?>0V5i9BcBKouH>`uYZg{I<4sK9kYl zJRHr<&2V>hM|*pRL4GX@p~6(*vb$PDScI(c));09mJpwaGS27UTnOps=BSq>+HPG>+b98(*qp@e* z+I3>KQD}k)3kye0O^q&(g6{9X`(Ce+406 zEX)#&!Vn5w$g7qE1>Z$1Kqk(fIm?4)CH(Xgb0sL6C@bSXpE`9KDJ%?<1I4oxRw>0S z!3bJ0pxH|fd3i^85Kd5#eEITa!vZF7=|7iv9Yk7M8jj?BBa|3OcXtn5STsVjs#44r zj0SNUhxY6qIwT_6+uNZE2}S0{O>E#@C7MlU#fp{4%F5<-Qr$yA;~TZ;O_`fSlS!B@ z7}3$u$;XBC^%msZ^ks0>gpO+hX=1qguHkP@M#GtCMpJZ z@79V%6k~D02>OF}uy}2;-(u!Es9Dflh{ul~8g`T07b@{Skm{nI- zkIgxo+3@fwcRS=d)uvz-!6ayrj~v^07VuJ6CAVVV-hH|tn-&}+!k2Pij>4YdxOeX! zpJge;qLo<+h00m?q$O#pCxEpm8b|e=T1#V9!StbxjAAApEh~pIuRN6bLRyU$>zG^e z$()bz!MpE6%^X=;o2PQ*;8xwL;yxDT(FO<|1ere7fp>RWCR)tg3JSWnY{|v7YuETV zYHdbLFnyTciJPpI{qc8yz~r~z!3+mSI4~jJeP=SJygm8#cH3+?&2++tAN(0pSU%aN zeKZx*r#ayLDevL8zy1x56%>e7Tb)r6jA(0Z#b4)sf|%F@o}&3{BBEmw5EUHw#8@@317o-ovhma)&AIm4Q`E`uTPEMBDuIdjI|dv0AHRaltw|I*^gE zfuG3PzyE+}9`jgSFp1k$x1m*Q;qT{<^73-gJZ7<)U=k%ICGhq3;pg?~g2PzH>Vgrp zyR&WUHn_REBReY_{R&V2*#aXXm_$oU3qQC*=UE3kNp0(ls9+Mb0UI2&1mR)fgYJ&o zDx)Hp#Noq-`S~*1oPF?M82YUbV%;P zg$ttDX0RE-BxrNi*V~u7A#@Yh*04Fj2)d9(hveK{-S{zcrI!$_flUe~(bCe2)X$b- zp{Ey)D?C?kX>3|B2|55Ayd;?4;GpY+qsf>MOoHxtdoJ+gZpi&naznHm- z_?@GJ2fh}~R%gr!CPBB-G?5y<8gik0Sw9&@?3}$gIsqumvJSS1l{_jYch0Ce5h|6P{OzrOybCq zZ}?@bf@79*ddIb31YKyYud5fc6*KMyvov