@pi-spi/app 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +28 -5
  2. package/assets/icons/qr.png +0 -0
  3. package/assets/sounds/voice/search_prompt_en.mp3 +0 -0
  4. package/assets/sounds/voice/search_prompt_fr.mp3 +0 -0
  5. package/assets/sounds/voice/search_prompt_pt.mp3 +0 -0
  6. package/example/app.json +9 -1
  7. package/lib/module/core/assets.js +1 -0
  8. package/lib/module/core/assets.js.map +1 -1
  9. package/lib/module/core/router.js +3 -0
  10. package/lib/module/core/router.js.map +1 -1
  11. package/lib/module/core/routes.js +14 -0
  12. package/lib/module/core/routes.js.map +1 -1
  13. package/lib/module/core/storage.js +27 -29
  14. package/lib/module/core/storage.js.map +1 -1
  15. package/lib/module/l10n/app_localizations.js.map +1 -1
  16. package/lib/module/l10n/app_localizations_en.js +21 -1
  17. package/lib/module/l10n/app_localizations_en.js.map +1 -1
  18. package/lib/module/l10n/app_localizations_fr.js +21 -1
  19. package/lib/module/l10n/app_localizations_fr.js.map +1 -1
  20. package/lib/module/l10n/app_localizations_pt.js +21 -1
  21. package/lib/module/l10n/app_localizations_pt.js.map +1 -1
  22. package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js +3 -3
  23. package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js.map +1 -1
  24. package/lib/module/modules/alias/presentation/pages/alias_page_otp.js.map +1 -1
  25. package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js +2 -2
  26. package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js.map +1 -1
  27. package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js +12 -10
  28. package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js.map +1 -1
  29. package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js +14 -9
  30. package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js.map +1 -1
  31. package/lib/module/modules/home/presentation/pages/home_page.js +41 -2
  32. package/lib/module/modules/home/presentation/pages/home_page.js.map +1 -1
  33. package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js +10 -5
  34. package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js.map +1 -1
  35. package/lib/module/modules/notification/presentation/bloc/notification_bloc.js +11 -4
  36. package/lib/module/modules/notification/presentation/bloc/notification_bloc.js.map +1 -1
  37. package/lib/module/modules/notification/presentation/bloc/notification_state.js +7 -1
  38. package/lib/module/modules/notification/presentation/bloc/notification_state.js.map +1 -1
  39. package/lib/module/modules/notification/presentation/pages/notification_page_liste.js +6 -2
  40. package/lib/module/modules/notification/presentation/pages/notification_page_liste.js.map +1 -1
  41. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js +2 -2
  42. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js.map +1 -1
  43. package/lib/module/modules/profile/presentation/pages/web/profile_web_page.js.map +1 -1
  44. package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page.js.map +1 -1
  45. package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js +2 -2
  46. package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js.map +1 -1
  47. package/lib/module/modules/search/domain/models/fonctionnalite.js +199 -0
  48. package/lib/module/modules/search/domain/models/fonctionnalite.js.map +1 -0
  49. package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js +119 -0
  50. package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js.map +1 -0
  51. package/lib/module/modules/search/presentation/pages/search_page.js +359 -0
  52. package/lib/module/modules/search/presentation/pages/search_page.js.map +1 -0
  53. package/lib/module/modules/search/presentation/pages/search_page_voice_button.js +335 -0
  54. package/lib/module/modules/search/presentation/pages/search_page_voice_button.js.map +1 -0
  55. package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js +9 -4
  56. package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js.map +1 -1
  57. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js +13 -1
  58. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js.map +1 -1
  59. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js +7 -1
  60. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js.map +1 -1
  61. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js +216 -137
  62. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js.map +1 -1
  63. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js +6 -2
  64. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js.map +1 -1
  65. package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js +3 -3
  66. package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js.map +1 -1
  67. package/lib/module/shared/widgets/amount_widget.js +4 -1
  68. package/lib/module/shared/widgets/amount_widget.js.map +1 -1
  69. package/lib/module/shared/widgets/input_amount.js.map +1 -1
  70. package/lib/module/shared/widgets/input_text.js +2 -0
  71. package/lib/module/shared/widgets/input_text.js.map +1 -1
  72. package/lib/module/shared/widgets/menu_actions_widget.js +10 -1
  73. package/lib/module/shared/widgets/menu_actions_widget.js.map +1 -1
  74. package/lib/module/shared/widgets/pagination_loader_widget.js +29 -0
  75. package/lib/module/shared/widgets/pagination_loader_widget.js.map +1 -0
  76. package/lib/typescript/src/core/assets.d.ts +1 -0
  77. package/lib/typescript/src/core/assets.d.ts.map +1 -1
  78. package/lib/typescript/src/core/router.d.ts +1 -0
  79. package/lib/typescript/src/core/router.d.ts.map +1 -1
  80. package/lib/typescript/src/core/routes.d.ts.map +1 -1
  81. package/lib/typescript/src/core/storage.d.ts +11 -3
  82. package/lib/typescript/src/core/storage.d.ts.map +1 -1
  83. package/lib/typescript/src/l10n/app_localizations.d.ts +40 -0
  84. package/lib/typescript/src/l10n/app_localizations.d.ts.map +1 -1
  85. package/lib/typescript/src/l10n/app_localizations_en.d.ts.map +1 -1
  86. package/lib/typescript/src/l10n/app_localizations_fr.d.ts.map +1 -1
  87. package/lib/typescript/src/l10n/app_localizations_pt.d.ts.map +1 -1
  88. package/lib/typescript/src/modules/categorie/presentation/pages/categorie_select_widget.d.ts.map +1 -1
  89. package/lib/typescript/src/modules/compte/presentation/pages/solde_widget_with_qr.d.ts.map +1 -1
  90. package/lib/typescript/src/modules/home/presentation/pages/home_page.d.ts.map +1 -1
  91. package/lib/typescript/src/modules/home/presentation/pages/home_tab_compte_more_menu.d.ts.map +1 -1
  92. package/lib/typescript/src/modules/notification/presentation/bloc/notification_bloc.d.ts.map +1 -1
  93. package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts +5 -0
  94. package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts.map +1 -1
  95. package/lib/typescript/src/modules/notification/presentation/pages/notification_page_liste.d.ts.map +1 -1
  96. package/lib/typescript/src/modules/profile/presentation/pages/web/profile_web_page.d.ts.map +1 -1
  97. package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts +70 -0
  98. package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts.map +1 -0
  99. package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts +21 -0
  100. package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts.map +1 -0
  101. package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts +21 -0
  102. package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts.map +1 -0
  103. package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts +29 -0
  104. package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts.map +1 -0
  105. package/lib/typescript/src/modules/subscription/presentation/pages/subscription_menu_widget.d.ts.map +1 -1
  106. package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.d.ts.map +1 -1
  107. package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts +5 -0
  108. package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts.map +1 -1
  109. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts +5 -1
  110. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts.map +1 -1
  111. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.d.ts.map +1 -1
  112. package/lib/typescript/src/shared/widgets/amount_widget.d.ts +3 -1
  113. package/lib/typescript/src/shared/widgets/amount_widget.d.ts.map +1 -1
  114. package/lib/typescript/src/shared/widgets/input_text.d.ts +3 -3
  115. package/lib/typescript/src/shared/widgets/input_text.d.ts.map +1 -1
  116. package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts +5 -1
  117. package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts.map +1 -1
  118. package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts +7 -0
  119. package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts.map +1 -0
  120. package/package.json +49 -35
  121. package/src/core/assets.ts +1 -0
  122. package/src/core/router.tsx +3 -0
  123. package/src/core/routes.tsx +15 -0
  124. package/src/core/storage.ts +30 -22
  125. package/src/l10n/app_localizations.ts +40 -0
  126. package/src/l10n/app_localizations_en.ts +20 -0
  127. package/src/l10n/app_localizations_fr.ts +20 -0
  128. package/src/l10n/app_localizations_pt.ts +20 -0
  129. package/src/modules/alias/presentation/pages/alias_page_create_options.tsx +3 -3
  130. package/src/modules/alias/presentation/pages/alias_page_otp.tsx +2 -2
  131. package/src/modules/categorie/presentation/pages/categorie_edit_icon_btn.tsx +2 -2
  132. package/src/modules/categorie/presentation/pages/categorie_select_widget.tsx +13 -9
  133. package/src/modules/compte/presentation/pages/solde_widget_with_qr.tsx +10 -3
  134. package/src/modules/home/presentation/pages/home_page.tsx +31 -2
  135. package/src/modules/home/presentation/pages/home_tab_compte_more_menu.tsx +10 -5
  136. package/src/modules/notification/presentation/bloc/notification_bloc.ts +12 -3
  137. package/src/modules/notification/presentation/bloc/notification_state.ts +11 -1
  138. package/src/modules/notification/presentation/pages/notification_page_liste.tsx +4 -1
  139. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx +2 -2
  140. package/src/modules/profile/presentation/pages/web/profile_web_page.tsx +3 -2
  141. package/src/modules/qrcode/presentation/pages/qrcode_alias_page.tsx +2 -2
  142. package/src/modules/qrcode/presentation/pages/qrcode_alias_page_share.tsx +2 -2
  143. package/src/modules/search/domain/models/fonctionnalite.ts +217 -0
  144. package/src/modules/search/presentation/pages/search_fonctionnalites.ts +135 -0
  145. package/src/modules/search/presentation/pages/search_page.tsx +314 -0
  146. package/src/modules/search/presentation/pages/search_page_voice_button.tsx +283 -0
  147. package/src/modules/subscription/presentation/pages/subscription_menu_widget.tsx +9 -4
  148. package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.ts +15 -1
  149. package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.ts +10 -1
  150. package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.tsx +204 -101
  151. package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.tsx +4 -1
  152. package/src/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.tsx +3 -3
  153. package/src/shared/widgets/amount_widget.tsx +5 -2
  154. package/src/shared/widgets/input_amount.tsx +2 -2
  155. package/src/shared/widgets/input_text.tsx +8 -3
  156. package/src/shared/widgets/menu_actions_widget.tsx +8 -2
  157. package/src/shared/widgets/pagination_loader_widget.tsx +24 -0
@@ -2,7 +2,7 @@ import { MaterialIcons } from '@expo/vector-icons';
2
2
  import { File, Paths } from 'expo-file-system';
3
3
  import * as Sharing from 'expo-sharing';
4
4
  import { useRef, useState } from 'react';
5
- import { ActivityIndicator, Platform, Pressable, ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native';
5
+ import { ActivityIndicator, Platform, Pressable, ScrollView, StyleSheet, useWindowDimensions, View, type ViewInstance } from 'react-native';
6
6
  import { captureRef } from 'react-native-view-shot';
7
7
 
8
8
  import { useBloc, useBlocListener, useCreateBloc } from '../../../../core/bloc_react';
@@ -42,7 +42,7 @@ export function QrCodeAliasPage() {
42
42
  const aliasCle = alias?.getCle() ?? '';
43
43
  const [qrCode, setQrCode] = useState<string | null>(null);
44
44
  const [showQrcodeName, setShowQrcodeName] = useState(false);
45
- const qrRef = useRef<View>(null);
45
+ const qrRef = useRef<ViewInstance>(null);
46
46
  const bloc = useCreateBloc(() => {
47
47
  const created = new QrcodeAliasBloc(Di.getQrcodeInputPort());
48
48
  if (alias != null) created.add(new QrcodeAliasEncodeEvent(alias));
@@ -5,7 +5,7 @@ import { Images } from '../../../../core/assets';
5
5
  import { AppRouter, useAppRouter } from '../../../../core/router';
6
6
  import { useTheme } from '../../../../core/theme';
7
7
  import { useAppLocalizations } from '../../../../l10n/localizations';
8
- import { MenuActionItem, MenuActionsWiget } from '../../../../shared/widgets/menu_actions_widget';
8
+ import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
9
9
 
10
10
  /** Feuille « Partager » : le QR code (image) ou l'alias (texte). */
11
11
  export function QrcodeAliasPageShare({
@@ -28,7 +28,7 @@ export function QrcodeAliasPageShare({
28
28
  <ScrollView contentContainerStyle={[styles.content, { paddingBottom: 20 + insets.bottom }]}>
29
29
  <Text style={theme.textTheme.headlineMedium}>{traductions.qrcodePagePartageTitle}</Text>
30
30
  <View style={styles.gap20} />
31
- <MenuActionsWiget
31
+ <MenuActionsWidget
32
32
  items={[
33
33
  new MenuActionItem(
34
34
  Images.homeFooterQrcode,
@@ -0,0 +1,217 @@
1
+ /** Lettres accentuées et ligatures ramenées à leur forme simple. */
2
+ const ACCENTS: Record<string, string> = {
3
+ à: 'a',
4
+ â: 'a',
5
+ ä: 'a',
6
+ á: 'a',
7
+ ã: 'a',
8
+ å: 'a',
9
+ ç: 'c',
10
+ é: 'e',
11
+ è: 'e',
12
+ ê: 'e',
13
+ ë: 'e',
14
+ î: 'i',
15
+ ï: 'i',
16
+ í: 'i',
17
+ ì: 'i',
18
+ ô: 'o',
19
+ ö: 'o',
20
+ ó: 'o',
21
+ ò: 'o',
22
+ õ: 'o',
23
+ ù: 'u',
24
+ û: 'u',
25
+ ü: 'u',
26
+ ú: 'u',
27
+ ÿ: 'y',
28
+ ñ: 'n',
29
+ œ: 'oe',
30
+ æ: 'ae',
31
+ };
32
+
33
+ /**
34
+ * Une fonctionnalité de l'application que l'utilisateur peut retrouver
35
+ * depuis la recherche.
36
+ *
37
+ * La recherche ignore les majuscules, les accents et la ponctuation :
38
+ * « Sécurité », « securite » et « SECURITÉ » donnent le même résultat.
39
+ */
40
+ export class Fonctionnalite {
41
+ /** Icône (module Metro, voir `Images`). */
42
+ readonly icon: number;
43
+
44
+ /** Libellé affiché, déjà traduit. C'est le champ le plus pertinent. */
45
+ readonly titre: string;
46
+
47
+ /** Courte description affichée sous le titre, déjà traduite. */
48
+ readonly sousTitre?: string;
49
+
50
+ /**
51
+ * Synonymes non affichés, dans toutes les langues de l'application.
52
+ * Ils doivent être écrits sans accents (voir {@link Fonctionnalite.normaliser}).
53
+ */
54
+ readonly motsCles: readonly string[];
55
+
56
+ /**
57
+ * Ouvre la fonctionnalité. Appelé par la page appelante une fois la
58
+ * recherche fermée, jamais par la page de recherche elle-même.
59
+ */
60
+ readonly ouvrir: () => void;
61
+
62
+ constructor(options: {
63
+ icon: number;
64
+ titre: string;
65
+ sousTitre?: string;
66
+ motsCles?: readonly string[];
67
+ ouvrir: () => void;
68
+ }) {
69
+ this.icon = options.icon;
70
+ this.titre = options.titre;
71
+ this.sousTitre = options.sousTitre;
72
+ this.motsCles = options.motsCles ?? [];
73
+ this.ouvrir = options.ouvrir;
74
+ }
75
+
76
+ /**
77
+ * Met [texte] en minuscules sans accents, et remplace tout ce qui
78
+ * n'est ni lettre ni chiffre par un seul espace.
79
+ * Exemple : « Code QR — Scanner ! » devient « code qr scanner ».
80
+ */
81
+ static normaliser(texte: string): string {
82
+ let resultat = '';
83
+ for (const lettre of texte.toLowerCase()) {
84
+ resultat += ACCENTS[lettre] ?? lettre;
85
+ }
86
+ return resultat.replace(/[^a-z0-9]+/g, ' ').trim();
87
+ }
88
+
89
+ /** Mots normalisés de [texte], sans les vides. */
90
+ private static mots(texte: string): string[] {
91
+ return Fonctionnalite.normaliser(texte)
92
+ .split(' ')
93
+ .filter((mot) => mot.length > 0);
94
+ }
95
+
96
+ /**
97
+ * Mots utiles de la saisie. Si elle ne contient que des mots courants
98
+ * (« de » en cours de frappe de « demander »), ils sont tous gardés.
99
+ */
100
+ private static motsUtiles(recherche: string): string[] {
101
+ const mots = Fonctionnalite.mots(recherche);
102
+ const utiles = mots.filter((mot) => !MOTS_VIDES.has(mot));
103
+ return utiles.length === 0 ? mots : utiles;
104
+ }
105
+
106
+ /**
107
+ * Qualité de la correspondance entre un mot [saisi] et un [mot] de la
108
+ * fonctionnalité, 0 s'ils ne correspondent pas :
109
+ * - 3 : mots identiques ;
110
+ * - 2 : [mot] commence par [saisi] (frappe en cours : « envo » trouve
111
+ * « envoi »), ou l'inverse pour un [mot] d'au moins 4 lettres
112
+ * (pluriel : « abonnements » trouve « abonnement ») ;
113
+ * - 1 : une seule faute de frappe, sur des mots d'au moins 5 lettres
114
+ * (« securiter », « abonement »).
115
+ */
116
+ private static qualite(saisi: string, mot: string): number {
117
+ if (saisi === mot) return 3;
118
+ if (mot.startsWith(saisi)) return 2;
119
+ if (mot.length >= 4 && saisi.startsWith(mot)) return 2;
120
+ if (saisi.length >= 5 && mot.length >= 5 && uneFaute(saisi, mot)) return 1;
121
+ return 0;
122
+ }
123
+
124
+ /**
125
+ * Nombre de mots de la saisie trouvés dans la fonctionnalité, et points
126
+ * cumulés (qualité de la correspondance × poids du champ, la meilleure
127
+ * pour chaque mot saisi).
128
+ */
129
+ private score(saisis: readonly string[]): { trouves: number; points: number } {
130
+ const champs: [string[], number][] = [
131
+ [Fonctionnalite.mots(this.titre), POIDS_TITRE],
132
+ [this.motsCles.flatMap((motCle) => Fonctionnalite.mots(motCle)), POIDS_MOTS_CLES],
133
+ [Fonctionnalite.mots(this.sousTitre ?? ''), POIDS_SOUS_TITRE],
134
+ ];
135
+ let trouves = 0;
136
+ let points = 0;
137
+ for (const saisi of saisis) {
138
+ let meilleur = 0;
139
+ for (const [mots, poids] of champs) {
140
+ for (const mot of mots) {
141
+ meilleur = Math.max(meilleur, Fonctionnalite.qualite(saisi, mot) * poids);
142
+ }
143
+ }
144
+ if (meilleur > 0) {
145
+ trouves++;
146
+ points += meilleur;
147
+ }
148
+ }
149
+ return { trouves, points };
150
+ }
151
+
152
+ /**
153
+ * Fonctionnalités de [catalogue] dont au moins un mot utile de
154
+ * [recherche] est trouvé, triées par nombre de mots trouvés puis par
155
+ * points. À égalité, l'ordre du catalogue est conservé.
156
+ * Une saisie vide renvoie tout le catalogue.
157
+ */
158
+ static rechercher(catalogue: readonly Fonctionnalite[], recherche: string): Fonctionnalite[] {
159
+ const saisis = Fonctionnalite.motsUtiles(recherche);
160
+ if (saisis.length === 0) return [...catalogue];
161
+ return catalogue
162
+ .map((item, ordre) => ({ item, ordre, ...item.score(saisis) }))
163
+ .filter(({ trouves }) => trouves > 0)
164
+ .sort((a, b) => b.trouves - a.trouves || b.points - a.points || a.ordre - b.ordre)
165
+ .map(({ item }) => item);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Mots trop courants pour désigner une fonctionnalité, en français,
171
+ * anglais et portugais, sans accents. Ils sont ignorés dans la saisie,
172
+ * pour qu'une phrase dictée (« je veux envoyer de l'argent ») ne
173
+ * retienne que les mots utiles.
174
+ */
175
+ const MOTS_VIDES: ReadonlySet<string> = new Set([
176
+ // Français
177
+ 'je', 'j', 'tu', 'il', 'elle', 'on', 'nous', 'vous', 'me', 'm', 'te',
178
+ 't', 'se', 's', 'le', 'la', 'les', 'l', 'un', 'une', 'des', 'du', 'de',
179
+ 'd', 'au', 'aux', 'a', 'et', 'ou', 'en', 'y', 'pour', 'par', 'sur',
180
+ 'avec', 'dans', 'mon', 'ma', 'mes', 'ton', 'ta', 'tes', 'son', 'sa',
181
+ 'ses', 'notre', 'nos', 'votre', 'vos', 'ce', 'cet', 'cette', 'ces', 'c',
182
+ 'qu', 'que', 'qui', 'quoi', 'comment', 'est', 'veux', 'voudrais',
183
+ 'souhaite', 'aimerais', 'faire', 'peux', 'puis', 'voir', 'ne', 'n',
184
+ 'pas', 'svp', 'stp',
185
+ // Anglais
186
+ 'i', 'my', 'the', 'an', 'to', 'of', 'for', 'in', 'at', 'and', 'or',
187
+ 'is', 'want', 'would', 'like', 'how', 'can', 'do', 'please', 'make',
188
+ // Portugais
189
+ 'eu', 'o', 'os', 'as', 'um', 'uma', 'da', 'dos', 'das', 'no',
190
+ 'na', 'em', 'para', 'por', 'com', 'meu', 'minha', 'meus', 'minhas',
191
+ 'quero', 'queria', 'gostaria', 'como', 'fazer', 'ver', 'e',
192
+ ]);
193
+
194
+ /** Poids d'un mot trouvé selon le champ où il l'est. */
195
+ const POIDS_TITRE = 3;
196
+ const POIDS_MOTS_CLES = 2;
197
+ const POIDS_SOUS_TITRE = 1;
198
+
199
+ /** [a] et [b] ne diffèrent que d'une lettre remplacée, ajoutée ou retirée. */
200
+ function uneFaute(a: string, b: string): boolean {
201
+ if (Math.abs(a.length - b.length) > 1) return false;
202
+ const [court, long] = a.length <= b.length ? [a, b] : [b, a];
203
+ let i = 0;
204
+ let j = 0;
205
+ let fautes = 0;
206
+ while (i < court.length && j < long.length) {
207
+ if (court[i] === long[j]) {
208
+ i++;
209
+ j++;
210
+ continue;
211
+ }
212
+ if (++fautes > 1) return false;
213
+ if (court.length === long.length) i++;
214
+ j++;
215
+ }
216
+ return fautes + (long.length - j) <= 1;
217
+ }
@@ -0,0 +1,135 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ import { Images } from '../../../../core/assets';
4
+ import { AppRouter, type SdkRouter } from '../../../../core/router';
5
+ import type { AppLocalizations } from '../../../../l10n/app_localizations';
6
+ import { Fonctionnalite } from '../../domain/models/fonctionnalite';
7
+
8
+ /**
9
+ * Catalogue des fonctionnalités accessibles depuis la recherche.
10
+ *
11
+ * [router] est celui de la page qui ouvre la recherche : la navigation
12
+ * part de là une fois la recherche fermée.
13
+ * [ouvrirOnglet] change d'onglet sur l'accueil (0 : compte, 1 : abonnements).
14
+ * Seules les pages présentes dans le SDK sont proposées (pas de
15
+ * sécurité ni d'à-propos : c'est l'app hôte qui les gère).
16
+ *
17
+ * L'ordre de la liste sert d'ordre d'affichage à pertinence égale :
18
+ * les fonctionnalités les plus utilisées sont en tête.
19
+ * Pour en ajouter une : titre et sous-titre traduits, mots-clés sans
20
+ * accents en français, anglais et portugais.
21
+ */
22
+ export function fonctionnalitesRecherchables(
23
+ router: SdkRouter,
24
+ traductions: AppLocalizations,
25
+ { ouvrirOnglet }: { ouvrirOnglet: (onglet: number) => void },
26
+ ): Fonctionnalite[] {
27
+ const aller = (route: string, params?: unknown) => void AppRouter.push(router, route, { params });
28
+ const web = Platform.OS === 'web';
29
+
30
+ return [
31
+ // Paiements
32
+ new Fonctionnalite({
33
+ icon: Images.arrowUp,
34
+ titre: traductions.homeActionSend,
35
+ sousTitre: traductions.searchSendSubTitle,
36
+ motsCles: ['envoi', 'transfert', 'payer', 'virement', 'send', 'pay', 'transfer', 'enviar', 'pagar'],
37
+ ouvrir: () => aller(AppRouter.transactionSend),
38
+ }),
39
+ new Fonctionnalite({
40
+ icon: Images.arrowDown,
41
+ titre: traductions.homeActionRequest,
42
+ sousTitre: traductions.searchReceiveSubTitle,
43
+ motsCles: ['demander', 'demande', 'rtp', 'request', 'receive', 'pedir', 'receber'],
44
+ ouvrir: () => aller(AppRouter.transactionReceive),
45
+ }),
46
+ new Fonctionnalite({
47
+ icon: Images.homeFooterQrcode,
48
+ titre: traductions.searchQrScanTitle,
49
+ sousTitre: traductions.searchQrScanSubTitle,
50
+ motsCles: ['qr', 'qrcode', 'scanner', 'scan', 'camera'],
51
+ // Le QR scanné ouvre directement l'envoi, comme depuis l'accueil.
52
+ ouvrir: () => aller(AppRouter.qrcodeScan, 'send_now'),
53
+ }),
54
+ new Fonctionnalite({
55
+ icon: Images.homeFooterQr,
56
+ titre: traductions.qrcodePagePartageQrCodeTitle,
57
+ sousTitre: traductions.searchQrShowSubTitle,
58
+ motsCles: ['qr', 'qrcode', 'partager', 'share', 'partilhar'],
59
+ ouvrir: () => aller(AppRouter.qrcodeShow),
60
+ }),
61
+ new Fonctionnalite({
62
+ icon: Images.iconSend,
63
+ titre: traductions.transactionSearchTitle,
64
+ sousTitre: traductions.searchTransactionsSubTitle,
65
+ motsCles: ['historique', 'paiement', 'operation', 'releve', 'history', 'payment', 'historico', 'pagamento'],
66
+ ouvrir: () => aller(AppRouter.transactionSearch),
67
+ }),
68
+
69
+ // Abonnements
70
+ new Fonctionnalite({
71
+ icon: Images.transactionPlanifier,
72
+ titre: traductions.homeActionMoreSheetProgrammerTitle,
73
+ sousTitre: traductions.homeActionMoreSheetProgrammerSubTitle,
74
+ motsCles: ['programmer', 'planifier', 'recurrent', 'schedule', 'agendar'],
75
+ ouvrir: () => aller(AppRouter.subscriptionSchedule),
76
+ }),
77
+ new Fonctionnalite({
78
+ icon: Images.homeMoreFindSubscription,
79
+ titre: traductions.homePageToolbarTabbarAbonnement,
80
+ sousTitre: traductions.searchAbonnementsSubTitle,
81
+ motsCles: ['abonnement', 'echeance', 'programme', 'subscription', 'assinatura'],
82
+ ouvrir: () => ouvrirOnglet(1),
83
+ }),
84
+ new Fonctionnalite({
85
+ icon: Images.homeMoreFindSubscription,
86
+ titre: traductions.homeActionMoreSheetAbonnementTitle,
87
+ sousTitre: traductions.homeActionMoreSheetAbonnementSubTitle,
88
+ motsCles: ['abonnement', 'subscription', 'assinatura'],
89
+ ouvrir: () => aller(AppRouter.subscriptionSubscribe),
90
+ }),
91
+ new Fonctionnalite({
92
+ icon: Images.homeMoreSplitPayments,
93
+ titre: traductions.homeActionMoreSheetPartagerTitle,
94
+ sousTitre: traductions.homeActionMoreSheetPartagerSubTitle,
95
+ motsCles: ['partager', 'repartir', 'diviser', 'amis', 'split', 'share', 'dividir'],
96
+ ouvrir: () => aller(AppRouter.transactionSplitPaymentTx),
97
+ }),
98
+
99
+ // Profil
100
+ new Fonctionnalite({
101
+ icon: Images.iconsCircleUser,
102
+ titre: traductions.profilePageMenuCompteTitle,
103
+ sousTitre: traductions.searchCompteSubTitle,
104
+ motsCles: ['profil', 'compte', 'iban', 'rib', 'profile', 'account', 'perfil', 'conta'],
105
+ ouvrir: () => aller(AppRouter.profileCompte),
106
+ }),
107
+ new Fonctionnalite({
108
+ icon: Images.iconsSetting,
109
+ titre: traductions.profilePageMenuParametreTitle,
110
+ sousTitre: traductions.searchParametreSubTitle,
111
+ motsCles: ['parametre', 'reglage', 'theme', 'langue', 'notification', 'sombre', 'settings', 'language', 'dark', 'definicoes', 'idioma'],
112
+ ouvrir: () => aller(AppRouter.profileParametre),
113
+ }),
114
+ // Sur le web, le profil n'a pas ces pages : pas de contacts,
115
+ // et l'aide est un lien externe.
116
+ ...(web
117
+ ? []
118
+ : [
119
+ new Fonctionnalite({
120
+ icon: Images.iconTransactionSendByAlias,
121
+ titre: traductions.profilePageMenuAliasContactTitle,
122
+ sousTitre: traductions.searchContactsSubTitle,
123
+ motsCles: ['contact', 'alias', 'repertoire', 'contacts', 'contactos'],
124
+ ouvrir: () => aller(AppRouter.profileContacts),
125
+ }),
126
+ new Fonctionnalite({
127
+ icon: Images.iconsHelp,
128
+ titre: traductions.profilePageMenuHelpTitle,
129
+ sousTitre: traductions.searchAideSubTitle,
130
+ motsCles: ['aide', 'faq', 'assistance', 'support', 'help', 'ajuda'],
131
+ ouvrir: () => aller(AppRouter.profileHelpCenter),
132
+ }),
133
+ ]),
134
+ ];
135
+ }