@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
@@ -1,5 +1,5 @@
1
1
  import AsyncStorage from '@react-native-async-storage/async-storage';
2
- import CryptoJS from 'crypto-js';
2
+ import { cbc } from '@noble/ciphers/aes.js';
3
3
  import * as Crypto from 'expo-crypto';
4
4
  import * as SecureStore from 'expo-secure-store';
5
5
 
@@ -95,7 +95,7 @@ export class Box {
95
95
  constructor(
96
96
  readonly name: string,
97
97
  private readonly entries: Map<string, Item>,
98
- private readonly dek: CryptoJS.lib.WordArray,
98
+ private readonly dek: Uint8Array,
99
99
  ) {}
100
100
 
101
101
  get length(): number {
@@ -142,7 +142,7 @@ export class Box {
142
142
  return `${KEY_PREFIX}box.${name}`;
143
143
  }
144
144
 
145
- static async open(name: string, dek: CryptoJS.lib.WordArray): Promise<Box> {
145
+ static async open(name: string, dek: Uint8Array): Promise<Box> {
146
146
  const raw = await AsyncStorage.getItem(Box.storageKey(name));
147
147
  let entries = new Map<string, Item>();
148
148
  if (raw != null) {
@@ -159,26 +159,34 @@ export class Box {
159
159
  }
160
160
  }
161
161
 
162
- function encrypt(plain: string, key: CryptoJS.lib.WordArray): string {
163
- const iv = bytesToWordArray(Crypto.getRandomBytes(16));
164
- const encrypted = CryptoJS.AES.encrypt(plain, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
165
- return `${CryptoJS.enc.Base64.stringify(iv)}:${encrypted.ciphertext.toString(CryptoJS.enc.Base64)}`;
162
+ /**
163
+ * Chiffre en AES-256-CBC (padding PKCS7) : `<iv base64>:<texte chiffré base64>`.
164
+ * Même format que l'ancienne implémentation `crypto-js` : les données déjà
165
+ * enregistrées restent lisibles.
166
+ */
167
+ export function encrypt(plain: string, key: Uint8Array): string {
168
+ const iv = Crypto.getRandomBytes(16);
169
+ const ciphertext = cbc(key, iv).encrypt(new TextEncoder().encode(plain));
170
+ return `${toBase64(iv)}:${toBase64(ciphertext)}`;
166
171
  }
167
172
 
168
- function decrypt(payload: string, key: CryptoJS.lib.WordArray): string {
173
+ export function decrypt(payload: string, key: Uint8Array): string {
169
174
  const [ivB64, cipherB64] = payload.split(':');
170
- const iv = CryptoJS.enc.Base64.parse(ivB64);
171
- const params = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(cipherB64) });
172
- const plain = CryptoJS.AES.decrypt(params, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
173
- return plain.toString(CryptoJS.enc.Utf8);
175
+ const plain = cbc(key, fromBase64(ivB64)).decrypt(fromBase64(cipherB64));
176
+ return new TextDecoder().decode(plain);
174
177
  }
175
178
 
176
- function bytesToWordArray(bytes: Uint8Array): CryptoJS.lib.WordArray {
177
- const words: number[] = [];
178
- for (let i = 0; i < bytes.length; i++) {
179
- words[i >>> 2] = (words[i >>> 2] ?? 0) | (bytes[i] << (24 - (i % 4) * 8));
180
- }
181
- return CryptoJS.lib.WordArray.create(words, bytes.length);
179
+ export function toBase64(bytes: Uint8Array): string {
180
+ let binary = '';
181
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
182
+ return btoa(binary);
183
+ }
184
+
185
+ export function fromBase64(value: string): Uint8Array {
186
+ const binary = atob(value);
187
+ const bytes = new Uint8Array(binary.length);
188
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
189
+ return bytes;
182
190
  }
183
191
 
184
192
  /**
@@ -195,7 +203,7 @@ export class AppStorage {
195
203
  static readonly keyId = 'DEK';
196
204
 
197
205
  /** The Data Encryption Key */
198
- private static dek: CryptoJS.lib.WordArray;
206
+ private static dek: Uint8Array;
199
207
 
200
208
  /** Collections ouvertes (chargées entièrement en mémoire, comme les boîtes Hive). */
201
209
  static collections = new Map<string, Promise<Box>>();
@@ -206,12 +214,12 @@ export class AppStorage {
206
214
  const keyString = await secureStorage.read(AppStorage.keyId);
207
215
  if (keyString == null) {
208
216
  const key = Crypto.getRandomBytes(32);
209
- await secureStorage.write(AppStorage.keyId, CryptoJS.enc.Base64.stringify(bytesToWordArray(key)));
210
- AppStorage.dek = bytesToWordArray(key);
217
+ await secureStorage.write(AppStorage.keyId, toBase64(key));
218
+ AppStorage.dek = key;
211
219
  }
212
220
  // Sinon récupérer la clé existante
213
221
  else {
214
- AppStorage.dek = CryptoJS.enc.Base64.parse(keyString);
222
+ AppStorage.dek = fromBase64(keyString);
215
223
  }
216
224
  AppStorage.isInit = true;
217
225
  }
@@ -1027,6 +1027,8 @@ export interface AppLocalizations {
1027
1027
  readonly transactionSearchInputFilterCategoriesSensPayes: string;
1028
1028
  /** Apply */
1029
1029
  readonly transactionSearchInputFilterBtnAppliquer: string;
1030
+ /** Reset */
1031
+ readonly transactionSearchInputFilterBtnReinitialiser: string;
1030
1032
  /** Scan */
1031
1033
  readonly qrcodePageBtnScan: string;
1032
1034
  /** QR Code */
@@ -1343,6 +1345,44 @@ export interface AppLocalizations {
1343
1345
  notificationMAJMessage(systeme: string): string;
1344
1346
  /** Install */
1345
1347
  readonly notificationMAJBtn: string;
1348
+ /** What would you like to do? */
1349
+ readonly searchTitle: string;
1350
+ /** Search for a feature */
1351
+ readonly searchHint: string;
1352
+ /** No feature matches “{motCle}” */
1353
+ searchEmpty(motCle: string): string;
1354
+ /** Send money to a contact */
1355
+ readonly searchSendSubTitle: string;
1356
+ /** Request money from a contact */
1357
+ readonly searchReceiveSubTitle: string;
1358
+ /** Scan a QR code */
1359
+ readonly searchQrScanTitle: string;
1360
+ /** Pay by scanning a QR code */
1361
+ readonly searchQrScanSubTitle: string;
1362
+ /** Receive money with your QR code */
1363
+ readonly searchQrShowSubTitle: string;
1364
+ /** Find a payment sent or received */
1365
+ readonly searchTransactionsSubTitle: string;
1366
+ /** See your scheduled payments */
1367
+ readonly searchAbonnementsSubTitle: string;
1368
+ /** Your details and your account */
1369
+ readonly searchCompteSubTitle: string;
1370
+ /** Theme, notifications and home screen */
1371
+ readonly searchParametreSubTitle: string;
1372
+ /** Manage your contacts and aliases */
1373
+ readonly searchContactsSubTitle: string;
1374
+ /** Frequently asked questions and support */
1375
+ readonly searchAideSubTitle: string;
1376
+ /** Voice search */
1377
+ readonly searchVoiceStart: string;
1378
+ /** Stop listening */
1379
+ readonly searchVoiceStop: string;
1380
+ /** Listening… */
1381
+ readonly searchVoiceListening: string;
1382
+ /** Tell me what you would like to do */
1383
+ readonly searchVoicePrompt: string;
1384
+ /** Voice search is not available. Check microphone access in settings. */
1385
+ readonly searchVoiceUnavailable: string;
1346
1386
  }
1347
1387
 
1348
1388
  export const supportedLocales = ['fr', 'en', 'pt'] as const;
@@ -524,6 +524,7 @@ Please try again later.`,
524
524
  transactionSearchInputFilterCategoriesSensRecus: `Received`,
525
525
  transactionSearchInputFilterCategoriesSensPayes: `Sent`,
526
526
  transactionSearchInputFilterBtnAppliquer: `Apply`,
527
+ transactionSearchInputFilterBtnReinitialiser: `Reset`,
527
528
  qrcodePageBtnScan: `Scan`,
528
529
  qrcodePageBtnMonCode: `QR Code`,
529
530
  qrcodePageBtnMonAlias: `My alias`,
@@ -683,4 +684,23 @@ Scanning will be automatic`,
683
684
  notificationMAJDate: (date: string) => `Since ${date}`,
684
685
  notificationMAJMessage: (systeme: string) => `A new version of the application is available on ${systeme}`,
685
686
  notificationMAJBtn: `Install`,
687
+ searchTitle: `What would you like to do?`,
688
+ searchHint: `Search for a feature`,
689
+ searchEmpty: (motCle: string) => `No feature matches “${motCle}”`,
690
+ searchSendSubTitle: `Send money to a contact`,
691
+ searchReceiveSubTitle: `Request money from a contact`,
692
+ searchQrScanTitle: `Scan a QR code`,
693
+ searchQrScanSubTitle: `Pay by scanning a QR code`,
694
+ searchQrShowSubTitle: `Receive money with your QR code`,
695
+ searchTransactionsSubTitle: `Find a payment sent or received`,
696
+ searchAbonnementsSubTitle: `See your scheduled payments`,
697
+ searchCompteSubTitle: `Your details and your account`,
698
+ searchParametreSubTitle: `Theme, notifications and home screen`,
699
+ searchContactsSubTitle: `Manage your contacts and aliases`,
700
+ searchAideSubTitle: `Frequently asked questions and support`,
701
+ searchVoiceStart: `Voice search`,
702
+ searchVoiceStop: `Stop listening`,
703
+ searchVoiceListening: `Listening…`,
704
+ searchVoicePrompt: `Tell me what you would like to do`,
705
+ searchVoiceUnavailable: `Voice search is not available. Check microphone access in settings.`,
686
706
  };
@@ -523,6 +523,7 @@ Merci de votre compréhension.`,
523
523
  transactionSearchInputFilterCategoriesSensRecus: `Reçus`,
524
524
  transactionSearchInputFilterCategoriesSensPayes: `Envoyés`,
525
525
  transactionSearchInputFilterBtnAppliquer: `Appliquer`,
526
+ transactionSearchInputFilterBtnReinitialiser: `Réinitialiser`,
526
527
  qrcodePageBtnScan: `Scan`,
527
528
  qrcodePageBtnMonCode: `Mon Code`,
528
529
  qrcodePageBtnMonAlias: `Mon alias`,
@@ -682,4 +683,23 @@ Le scan se fait automatiquement`,
682
683
  notificationMAJDate: (date: string) => `Depuis le ${date}`,
683
684
  notificationMAJMessage: (systeme: string) => `Une nouvelle version de l'application est disponible sur ${systeme}`,
684
685
  notificationMAJBtn: `Installer`,
686
+ searchTitle: `Que voulez-vous faire ?`,
687
+ searchHint: `Rechercher une fonctionnalité`,
688
+ searchEmpty: (motCle: string) => `Aucune fonctionnalité ne correspond à « ${motCle} »`,
689
+ searchSendSubTitle: `Envoyer de l’argent à un contact`,
690
+ searchReceiveSubTitle: `Demander de l’argent à un contact`,
691
+ searchQrScanTitle: `Scanner un QR code`,
692
+ searchQrScanSubTitle: `Payer en scannant un QR code`,
693
+ searchQrShowSubTitle: `Recevoir de l’argent avec votre QR code`,
694
+ searchTransactionsSubTitle: `Retrouver un paiement envoyé ou reçu`,
695
+ searchAbonnementsSubTitle: `Voir vos paiements programmés`,
696
+ searchCompteSubTitle: `Vos informations et votre compte`,
697
+ searchParametreSubTitle: `Thème, notifications et écran d’accueil`,
698
+ searchContactsSubTitle: `Gérer vos contacts et vos alias`,
699
+ searchAideSubTitle: `Questions fréquentes et assistance`,
700
+ searchVoiceStart: `Recherche vocale`,
701
+ searchVoiceStop: `Arrêter l’écoute`,
702
+ searchVoiceListening: `Je vous écoute…`,
703
+ searchVoicePrompt: `Dites-moi ce que vous souhaitez faire`,
704
+ searchVoiceUnavailable: `La recherche vocale n’est pas disponible. Vérifiez l’accès au micro dans les réglages.`,
685
705
  };
@@ -524,6 +524,7 @@ Por favor, tente novamente mais tarde`,
524
524
  transactionSearchInputFilterCategoriesSensRecus: `Recebidos`,
525
525
  transactionSearchInputFilterCategoriesSensPayes: `Enviados`,
526
526
  transactionSearchInputFilterBtnAppliquer: `Aplicar`,
527
+ transactionSearchInputFilterBtnReinitialiser: `Repor`,
527
528
  qrcodePageBtnScan: `Scan`,
528
529
  qrcodePageBtnMonCode: `Meu Código`,
529
530
  qrcodePageBtnMonAlias: `Meu alias`,
@@ -683,4 +684,23 @@ O scan é feito automaticamente`,
683
684
  notificationMAJDate: (date: string) => `Desde ${date}`,
684
685
  notificationMAJMessage: (systeme: string) => `Uma nova versão da aplicação está disponível em ${systeme}`,
685
686
  notificationMAJBtn: `Instalar`,
687
+ searchTitle: `O que deseja fazer?`,
688
+ searchHint: `Pesquisar uma funcionalidade`,
689
+ searchEmpty: (motCle: string) => `Nenhuma funcionalidade corresponde a «${motCle}»`,
690
+ searchSendSubTitle: `Enviar dinheiro a um contacto`,
691
+ searchReceiveSubTitle: `Pedir dinheiro a um contacto`,
692
+ searchQrScanTitle: `Digitalizar um código QR`,
693
+ searchQrScanSubTitle: `Pagar digitalizando um código QR`,
694
+ searchQrShowSubTitle: `Receber dinheiro com o seu código QR`,
695
+ searchTransactionsSubTitle: `Encontrar um pagamento enviado ou recebido`,
696
+ searchAbonnementsSubTitle: `Ver os seus pagamentos programados`,
697
+ searchCompteSubTitle: `Os seus dados e a sua conta`,
698
+ searchParametreSubTitle: `Tema, notificações e ecrã inicial`,
699
+ searchContactsSubTitle: `Gerir os seus contactos e alias`,
700
+ searchAideSubTitle: `Perguntas frequentes e assistência`,
701
+ searchVoiceStart: `Pesquisa por voz`,
702
+ searchVoiceStop: `Parar de ouvir`,
703
+ searchVoiceListening: `Estou a ouvir…`,
704
+ searchVoicePrompt: `Diga-me o que deseja fazer`,
705
+ searchVoiceUnavailable: `A pesquisa por voz não está disponível. Verifique o acesso ao microfone nas definições.`,
686
706
  };
@@ -4,7 +4,7 @@ import { Images } from '../../../../core/assets';
4
4
  import { useBloc } from '../../../../core/bloc_react';
5
5
  import { useTheme } from '../../../../core/theme';
6
6
  import { useAppLocalizations } from '../../../../l10n/localizations';
7
- import { MenuActionItem, MenuActionsWiget } from '../../../../shared/widgets/menu_actions_widget';
7
+ import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
8
8
  import { MyPageContainer } from '../../../../shared/widgets/my_page_container';
9
9
  import { AliasBloc } from '../bloc/alias_bloc';
10
10
  import { CreateAliasMBNOEvent, CreateAliasSHIDEvent } from '../bloc/alias_event';
@@ -26,7 +26,7 @@ export function AliasPageCreateOptions({ compte }: { compte: string }) {
26
26
  <View style={styles.gap32} />
27
27
 
28
28
  {/* Bouton créer alias SHID */}
29
- <MenuActionsWiget
29
+ <MenuActionsWidget
30
30
  items={[
31
31
  new MenuActionItem(
32
32
  Images.aliasIconUser,
@@ -40,7 +40,7 @@ export function AliasPageCreateOptions({ compte }: { compte: string }) {
40
40
  <View style={styles.gap20} />
41
41
 
42
42
  {/* - type MBNO */}
43
- <MenuActionsWiget
43
+ <MenuActionsWidget
44
44
  items={[
45
45
  new MenuActionItem(
46
46
  Images.aliasIconPhone,
@@ -1,5 +1,5 @@
1
1
  import { useRef, useState, type ReactNode } from 'react';
2
- import { Keyboard, StyleSheet, Text, TextInput, useWindowDimensions, View } from 'react-native';
2
+ import { Keyboard, StyleSheet, Text, TextInput, useWindowDimensions, View, type TextInputInstance } from 'react-native';
3
3
 
4
4
  import { useTheme } from '../../../../core/theme';
5
5
 
@@ -30,7 +30,7 @@ export function AliasPageOtp({
30
30
  const theme = useTheme();
31
31
  const { width: largeur } = useWindowDimensions();
32
32
  const [values, setValues] = useState<string[]>(() => Array(pinLength).fill(''));
33
- const inputs = useRef<(TextInput | null)[]>([]);
33
+ const inputs = useRef<(TextInputInstance | null)[]>([]);
34
34
 
35
35
  const isTablet = largeur >= 600;
36
36
  const paddingX = 20;
@@ -12,7 +12,7 @@ import { ElevatedButton } from '../../../../shared/material/buttons';
12
12
  import { Card } from '../../../../shared/material/card';
13
13
  import { showModalBottomSheet } from '../../../../shared/material/overlays';
14
14
  import { EmojiSelectWidget } from '../../../../shared/widgets/emoji_select_widget';
15
- import { MenuActionItem, MenuActionsWiget } from '../../../../shared/widgets/menu_actions_widget';
15
+ import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
16
16
  import { CategorieIconType } from '../../domain/models/categorie';
17
17
  import { CategorieBloc } from '../bloc/categorie_bloc';
18
18
  import { CategorieAddValidationEvent, CategorieEditValidationEvent } from '../bloc/categorie_event';
@@ -67,7 +67,7 @@ function CategorieEditIconOptions() {
67
67
  <ScrollView contentContainerStyle={styles.sheetContent}>
68
68
  <Text style={[theme.textTheme.headlineMedium, styles.title]}>{traductions.categorieFormIconSheetTitle}</Text>
69
69
  <Card>
70
- <MenuActionsWiget
70
+ <MenuActionsWidget
71
71
  items={[
72
72
  // Emoji
73
73
  new MenuActionItem(Images.categorieIconEmoji, traductions.categorieFormIconSheetEmojiTitle, null, () => void selectEmoji(), {
@@ -17,14 +17,18 @@ export function CategorieSelectWidget({
17
17
  const state = useBlocBuilder(categorieBloc);
18
18
  return (
19
19
  <View>
20
- {/* Catégories personnalisées */}
21
- <CategorieListeWidget
22
- liste={state.categories}
23
- editable={false}
24
- defaultSelectedCategories={selectedCategories}
25
- onSelectionChanged={onSelectionChanged}
26
- />
27
- <View style={styles.gap20} />
20
+ {/* Catégories personnalisées (pas de carte vide s'il n'y en a pas) */}
21
+ {state.categories.length > 0 && (
22
+ <>
23
+ <CategorieListeWidget
24
+ liste={state.categories}
25
+ editable={false}
26
+ defaultSelectedCategories={selectedCategories}
27
+ onSelectionChanged={onSelectionChanged}
28
+ />
29
+ <View style={styles.gap16} />
30
+ </>
31
+ )}
28
32
  {/* Catégories par défaut */}
29
33
  <CategorieListeWidget
30
34
  liste={Categorie.defaultListe}
@@ -35,4 +39,4 @@ export function CategorieSelectWidget({
35
39
  );
36
40
  }
37
41
 
38
- const styles = StyleSheet.create({ gap20: { height: 20 } });
42
+ const styles = StyleSheet.create({ gap16: { height: 16 } });
@@ -157,8 +157,6 @@ function SoldeRow({ soldeState, compteSoldeBloc }: { soldeState: CompteSoldeStat
157
157
  const traductions = useAppLocalizations();
158
158
  const configBloc = useBloc(ConfigBloc);
159
159
  const displayEye = configBloc.state.configParams.params[ConfigKey.hideEye] !== '1';
160
- const solde = soldeState.solde ?? 0;
161
- const fontSize = solde < 100000000 ? 24 : solde < 1000000000 ? 20 : 18;
162
160
 
163
161
  return (
164
162
  <View>
@@ -167,7 +165,15 @@ function SoldeRow({ soldeState, compteSoldeBloc }: { soldeState: CompteSoldeStat
167
165
  {soldeState instanceof CompteSoldeStateInitial && <SkeletonWidget height={25} width={150} />}
168
166
  {soldeState instanceof CompteSoldeDisplayState && (
169
167
  <>
170
- <AmountWidget montant={soldeState.solde} numberOfLines={1} style={{ ...theme.textTheme.headlineLarge, fontSize }} />
168
+ {/* Le montant prend la place disponible et se réduit s'il est trop long (8 chiffres et plus), au lieu de déborder */}
169
+ <View style={styles.amount}>
170
+ <AmountWidget
171
+ montant={soldeState.solde}
172
+ numberOfLines={1}
173
+ adjustsFontSizeToFit
174
+ style={{ ...theme.textTheme.headlineLarge, fontSize: 24 }}
175
+ />
176
+ </View>
171
177
  {displayEye && <EyeButton compteSoldeBloc={compteSoldeBloc} />}
172
178
  </>
173
179
  )}
@@ -281,6 +287,7 @@ const styles = StyleSheet.create({
281
287
  cardContent: { paddingLeft: 10, paddingRight: 10, paddingTop: 16, paddingBottom: 10 },
282
288
  row: { flexDirection: 'row', alignItems: 'flex-start' },
283
289
  inlineRow: { flexDirection: 'row', alignItems: 'center' },
290
+ amount: { flexShrink: 1 },
284
291
  expanded: { flex: 1 },
285
292
  libelle: { color: Themer.neural04Color, letterSpacing: 0.4 },
286
293
  bold: { fontWeight: '600' },
@@ -16,6 +16,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
16
16
  import { useBloc } from '../../../../core/bloc_react';
17
17
  import { CompteAliasStatus } from '../../../../core/compte_alias_status';
18
18
  import { Di } from '../../../../core/di';
19
+ import { AppRouter, useAppRouter } from '../../../../core/router';
19
20
  import { Themer, useTheme } from '../../../../core/theme';
20
21
  import { useValueListenable } from '../../../../core/value_notifier';
21
22
  import { useAppLocalizations } from '../../../../l10n/localizations';
@@ -24,6 +25,8 @@ import { ContactBloc } from '../../../contacts/presentation/bloc/contact_bloc';
24
25
  import { ContactListEvent } from '../../../contacts/presentation/bloc/contact_event';
25
26
  import { ParametreHideAmountBloc } from '../../../profile/presentation/bloc/hide_amount/hide_amount_bloc';
26
27
  import { ParametreHideAmountInitEvent } from '../../../profile/presentation/bloc/hide_amount/hide_amount_event';
28
+ import type { Fonctionnalite } from '../../../search/domain/models/fonctionnalite';
29
+ import { fonctionnalitesRecherchables } from '../../../search/presentation/pages/search_fonctionnalites';
27
30
  import { TransactionSendBloc } from '../../../transactions/presentation/bloc/transaction_send/transaction_send_bloc';
28
31
  import { TransactionSendUpdateStateEvent } from '../../../transactions/presentation/bloc/transaction_send/transaction_send_event';
29
32
  import { HomeTabCompte } from './home_tab_compte';
@@ -36,6 +39,7 @@ import { ComingSoon } from './pending_modules';
36
39
  export function HomePage({ selectedTab }: { selectedTab?: number }) {
37
40
  const traductions = useAppLocalizations();
38
41
  const theme = useTheme();
42
+ const router = useAppRouter();
39
43
  const insets = useSafeAreaInsets();
40
44
  const { width } = useWindowDimensions();
41
45
  const status = useValueListenable(Di.activeCompteAlias);
@@ -74,6 +78,17 @@ export function HomePage({ selectedTab }: { selectedTab?: number }) {
74
78
  listRef.current?.scrollToIndex({ index, animated: true });
75
79
  };
76
80
 
81
+ /**
82
+ * La recherche renvoie la fonctionnalité choisie au lieu de l'ouvrir :
83
+ * la navigation part de l'accueil, et le retour y ramène.
84
+ */
85
+ const ouvrirRecherche = async () => {
86
+ const choisie = await AppRouter.push<Fonctionnalite>(router, AppRouter.search, {
87
+ params: fonctionnalitesRecherchables(router, traductions, { ouvrirOnglet: goToTab }),
88
+ });
89
+ choisie?.ouvrir();
90
+ };
91
+
77
92
  const onMomentumScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
78
93
  setTab(Math.round(e.nativeEvent.contentOffset.x / pageWidth));
79
94
  };
@@ -91,8 +106,20 @@ export function HomePage({ selectedTab }: { selectedTab?: number }) {
91
106
  <View style={{ backgroundColor: theme.appBarTheme.backgroundColor }}>
92
107
  <View style={styles.toolbar}>
93
108
  <HomeToolbarLeading user={Di.config.user} enabled={hasAlias} />
94
- {/* Notifications (grisé si le compte actif n'a pas d'alias) */}
95
- <NotificationBtnOpenWidget enabled={hasAlias} />
109
+ <View style={styles.actions}>
110
+ {/* Recherche dans les fonctionnalités (grisé si le compte actif n'a pas d'alias) */}
111
+ <Pressable
112
+ accessibilityRole="button"
113
+ accessibilityLabel={traductions.searchTitle}
114
+ disabled={!hasAlias}
115
+ onPress={() => void ouvrirRecherche()}
116
+ style={[styles.action, { opacity: hasAlias ? 1 : 0.38 }]}
117
+ >
118
+ <MaterialIcons name="search" size={24} color={theme.appBarTheme.foregroundColor} />
119
+ </Pressable>
120
+ {/* Notifications (grisé si le compte actif n'a pas d'alias) */}
121
+ <NotificationBtnOpenWidget enabled={hasAlias} />
122
+ </View>
96
123
  </View>
97
124
  {/* Onglets */}
98
125
  <View style={styles.tabBar}>
@@ -161,6 +188,8 @@ export function HomePage({ selectedTab }: { selectedTab?: number }) {
161
188
 
162
189
  const styles = StyleSheet.create({
163
190
  toolbar: { height: 56, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
191
+ actions: { flexDirection: 'row', alignItems: 'center' },
192
+ action: { padding: 12 },
164
193
  tabBar: { flexDirection: 'row', paddingHorizontal: 20, height: 46, alignItems: 'center' },
165
194
  tab: { flex: 1, height: 36, marginHorizontal: 2, alignItems: 'center', justifyContent: 'center', borderRadius: 12.2 },
166
195
  tabSelected: { backgroundColor: '#151413' },
@@ -6,7 +6,7 @@ import { AppRouter, useAppRouter } from '../../../../core/router';
6
6
  import { useTheme } from '../../../../core/theme';
7
7
  import { useAppLocalizations } from '../../../../l10n/localizations';
8
8
  import { Card } from '../../../../shared/material/card';
9
- import { MenuActionItem, MenuActionsWiget } from '../../../../shared/widgets/menu_actions_widget';
9
+ import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
10
10
 
11
11
  /**
12
12
  * Menu « Plus » de l'accueil. Le participant peut personnaliser ce menu
@@ -18,6 +18,11 @@ export function HomeTabCompteMoreMenu() {
18
18
  const router = useAppRouter();
19
19
  const { height } = useWindowDimensions();
20
20
  const insets = useSafeAreaInsets();
21
+ // Fermer le menu avant d'ouvrir la page : sinon il reste affiché par-dessus
22
+ const open = (location: string) => {
23
+ AppRouter.pop(router);
24
+ void AppRouter.push(router, location);
25
+ };
21
26
  return (
22
27
  <View
23
28
  style={[
@@ -27,14 +32,14 @@ export function HomeTabCompteMoreMenu() {
27
32
  >
28
33
  <Card>
29
34
  <ScrollView>
30
- <MenuActionsWiget
35
+ <MenuActionsWidget
31
36
  items={[
32
37
  // Programmer un paiement
33
38
  new MenuActionItem(
34
39
  Images.homeFooterTransfer,
35
40
  traductions.homeActionMoreSheetProgrammerTitle,
36
41
  traductions.homeActionMoreSheetProgrammerSubTitle,
37
- () => void AppRouter.push(router, AppRouter.subscriptionSchedule),
42
+ () => open(AppRouter.subscriptionSchedule),
38
43
  { iconSize: 20 },
39
44
  ),
40
45
  // Trouver un abonnement
@@ -42,7 +47,7 @@ export function HomeTabCompteMoreMenu() {
42
47
  Images.homeMoreFindSubscription,
43
48
  traductions.homeActionMoreSheetAbonnementTitle,
44
49
  traductions.homeActionMoreSheetAbonnementSubTitle,
45
- () => void AppRouter.push(router, AppRouter.subscriptionSubscribe),
50
+ () => open(AppRouter.subscriptionSubscribe),
46
51
  { iconSize: 20 },
47
52
  ),
48
53
  // Partager dépenses
@@ -50,7 +55,7 @@ export function HomeTabCompteMoreMenu() {
50
55
  Images.homeMoreSplitPayments,
51
56
  traductions.homeActionMoreSheetPartagerTitle,
52
57
  traductions.homeActionMoreSheetPartagerSubTitle,
53
- () => void AppRouter.push(router, AppRouter.transactionSplitPaymentTx),
58
+ () => open(AppRouter.transactionSplitPaymentTx),
54
59
  { iconSize: 20 },
55
60
  ),
56
61
  ]}
@@ -147,8 +147,13 @@ export class NotificationBloc extends Bloc<NotificationEvent, NotificationState>
147
147
  private async onList(event: NotificationListEvent, emit: Emitter<NotificationState>): Promise<void> {
148
148
  // En cours de rechargement
149
149
  emit(new NotificationLoadingState(emptyListe(), this.state.count, event.command));
150
+
151
+ // Repartir de la première page
152
+ const command = event.command;
153
+ command.index = null;
154
+ command.total = 0;
155
+
150
156
  try {
151
- const command = event.command;
152
157
 
153
158
  // Rechercher d'abord en local et afficher
154
159
  const donneesLocales = await this.pNotificationInputPort.list(listParams(command));
@@ -186,7 +191,8 @@ export class NotificationBloc extends Bloc<NotificationEvent, NotificationState>
186
191
  if (this.paginating) return;
187
192
  this.paginating = true;
188
193
  const command = this.state.command;
189
- const oldListe = this.state.notifications;
194
+ // Affiche le loader en bas de la liste
195
+ emit(new NotificationInitialState(this.state.notifications, this.state.count, command, true));
190
196
  try {
191
197
  // Récupérer les notifications depuis le serveur
192
198
  const liste = await this.pNotificationInputPort.search(listParams(command, event.index));
@@ -196,16 +202,19 @@ export class NotificationBloc extends Bloc<NotificationEvent, NotificationState>
196
202
  if (newNotifications.length > 0) {
197
203
  emit(
198
204
  new NotificationInitialState(
199
- new NotificationListe([...oldListe.data, ...newNotifications], liste.meta),
205
+ new NotificationListe([...this.state.notifications.data, ...newNotifications], liste.meta),
200
206
  this.state.count,
201
207
  command,
202
208
  ),
203
209
  );
210
+ return;
204
211
  }
205
212
  } catch (e) {
206
213
  this.logger.e('Recuperer liste notification pagination error =>', { error: e });
207
214
  } finally {
208
215
  this.paginating = false;
209
216
  }
217
+ // Page vide ou erreur : on retire le loader
218
+ emit(new NotificationInitialState(this.state.notifications, this.state.count, command));
210
219
  }
211
220
  }
@@ -11,7 +11,17 @@ export abstract class NotificationState {
11
11
  ) {}
12
12
  }
13
13
 
14
- export class NotificationInitialState extends NotificationState {}
14
+ export class NotificationInitialState extends NotificationState {
15
+ constructor(
16
+ notifications: NotificationListe,
17
+ count: number,
18
+ command: NotificationSearchCommand,
19
+ /** true pendant le chargement de la page suivante : un petit loader est affiché en bas de la liste. */
20
+ readonly isPaginating = false,
21
+ ) {
22
+ super(notifications, count, command);
23
+ }
24
+ }
15
25
 
16
26
  export class NotificationLoadingState extends NotificationState {}
17
27
 
@@ -6,9 +6,10 @@ import { Themer, useTheme } from '../../../../core/theme';
6
6
  import { useAppLocalizations } from '../../../../l10n/localizations';
7
7
  import { Card } from '../../../../shared/material/card';
8
8
  import { DateFormat } from '../../../../shared/utils/date_format';
9
+ import { PaginationLoaderWidget } from '../../../../shared/widgets/pagination_loader_widget';
9
10
  import { NotificationBloc } from '../bloc/notification_bloc';
10
11
  import { NotificationListEvent, NotificationSearchPaginateEvent } from '../bloc/notification_event';
11
- import { NotificationLoadingState } from '../bloc/notification_state';
12
+ import { NotificationInitialState, NotificationLoadingState } from '../bloc/notification_state';
12
13
  import { groupNotifications } from './notification_groups';
13
14
  import { NotificationPageListeEmpty } from './notification_page_liste_empty';
14
15
  import { NotificationPageListeItem } from './notification_page_liste_item';
@@ -54,6 +55,8 @@ export function NotificationPageListe() {
54
55
  if (command.canLoadMore()) bloc.add(new NotificationSearchPaginateEvent(command.index));
55
56
  }}
56
57
  onEndReachedThreshold={0.2}
58
+ // Chargement de la page suivante
59
+ ListFooterComponent={state instanceof NotificationInitialState && state.isPaginating ? <PaginationLoaderWidget /> : undefined}
57
60
  renderItem={({ item }) => (
58
61
  <View>
59
62
  {/* Jour */}
@@ -10,7 +10,7 @@ import { AppRouter, useAppRouter } from '../../../../../core/router';
10
10
  import { Themer, useTheme } from '../../../../../core/theme';
11
11
  import { useAppLocalizations } from '../../../../../l10n/localizations';
12
12
  import { Card } from '../../../../../shared/material/card';
13
- import { MenuActionItem, MenuActionsWiget } from '../../../../../shared/widgets/menu_actions_widget';
13
+ import { MenuActionItem, MenuActionsWidget } from '../../../../../shared/widgets/menu_actions_widget';
14
14
  import { ConfigBloc } from '../../../../config/adapters/ui/bloc/config_bloc';
15
15
  import { ConfigChangeEvent } from '../../../../config/adapters/ui/bloc/config_event';
16
16
  import { ConfigLoadedState } from '../../../../config/adapters/ui/bloc/config_state';
@@ -116,7 +116,7 @@ export function ProfileParametrePageNotifications() {
116
116
  <View style={styles.gap20} />
117
117
  <Text style={theme.textTheme.headlineMedium}>{traductions.appSettingPageMenuNotificationSonTitle}</Text>
118
118
  <View style={styles.gap10} />
119
- <MenuActionsWiget
119
+ <MenuActionsWidget
120
120
  items={[
121
121
  new MenuActionItem(
122
122
  Images.iconsCloche,
@@ -23,7 +23,7 @@ export function ProfileWebPage({ url }: { url: string }) {
23
23
  const theme = useTheme();
24
24
  const router = useAppRouter();
25
25
  const insets = useSafeAreaInsets();
26
- const webview = useRef<WebView>(null);
26
+ const webview = useRef<WebView<object>>(null);
27
27
  const [loading, setLoading] = useState(true);
28
28
  const [hasError, setHasError] = useState(false);
29
29
 
@@ -47,7 +47,8 @@ export function ProfileWebPage({ url }: { url: string }) {
47
47
  />
48
48
  ) : null}
49
49
  <View style={hasError ? styles.hidden : styles.flex}>
50
- <WebView
50
+ {/* react-native-webview 14 : sans paramètre de type, ses props valent `never` */}
51
+ <WebView<object>
51
52
  ref={webview}
52
53
  source={{ uri: url }}
53
54
  style={styles.web}