@pi-spi/app 1.3.1 → 1.5.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 (223) hide show
  1. package/README.md +74 -7
  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.tsx +16 -1
  7. package/example/app.json +9 -1
  8. package/lib/module/core/assets.js +1 -0
  9. package/lib/module/core/assets.js.map +1 -1
  10. package/lib/module/core/di.js +8 -1
  11. package/lib/module/core/di.js.map +1 -1
  12. package/lib/module/core/pi_spi_custom_theme.js +63 -0
  13. package/lib/module/core/pi_spi_custom_theme.js.map +1 -0
  14. package/lib/module/core/pi_spi_sdk_config.js +15 -3
  15. package/lib/module/core/pi_spi_sdk_config.js.map +1 -1
  16. package/lib/module/core/router.js +3 -0
  17. package/lib/module/core/router.js.map +1 -1
  18. package/lib/module/core/routes.js +14 -0
  19. package/lib/module/core/routes.js.map +1 -1
  20. package/lib/module/core/storage.js +27 -29
  21. package/lib/module/core/storage.js.map +1 -1
  22. package/lib/module/core/theme.js +51 -0
  23. package/lib/module/core/theme.js.map +1 -1
  24. package/lib/module/index.js +1 -0
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/module/l10n/app_localizations.js.map +1 -1
  27. package/lib/module/l10n/app_localizations_en.js +24 -1
  28. package/lib/module/l10n/app_localizations_en.js.map +1 -1
  29. package/lib/module/l10n/app_localizations_fr.js +24 -1
  30. package/lib/module/l10n/app_localizations_fr.js.map +1 -1
  31. package/lib/module/l10n/app_localizations_pt.js +24 -1
  32. package/lib/module/l10n/app_localizations_pt.js.map +1 -1
  33. package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js +3 -3
  34. package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js.map +1 -1
  35. package/lib/module/modules/alias/presentation/pages/alias_page_otp.js.map +1 -1
  36. package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js +2 -2
  37. package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js.map +1 -1
  38. package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js +12 -10
  39. package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js.map +1 -1
  40. package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js +14 -9
  41. package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js.map +1 -1
  42. package/lib/module/modules/home/presentation/pages/home_page.js +41 -2
  43. package/lib/module/modules/home/presentation/pages/home_page.js.map +1 -1
  44. package/lib/module/modules/home/presentation/pages/home_tab_compte.js +1 -2
  45. package/lib/module/modules/home/presentation/pages/home_tab_compte.js.map +1 -1
  46. package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js +2 -2
  47. package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js.map +1 -1
  48. package/lib/module/modules/introduction/presentation/pages/introduction_bar.js +8 -1
  49. package/lib/module/modules/introduction/presentation/pages/introduction_bar.js.map +1 -1
  50. package/lib/module/modules/introduction/presentation/pages/introduction_page.js +29 -4
  51. package/lib/module/modules/introduction/presentation/pages/introduction_page.js.map +1 -1
  52. package/lib/module/modules/notification/presentation/bloc/notification_bloc.js +11 -4
  53. package/lib/module/modules/notification/presentation/bloc/notification_bloc.js.map +1 -1
  54. package/lib/module/modules/notification/presentation/bloc/notification_state.js +7 -1
  55. package/lib/module/modules/notification/presentation/bloc/notification_state.js.map +1 -1
  56. package/lib/module/modules/notification/presentation/pages/notification_page_liste.js +6 -2
  57. package/lib/module/modules/notification/presentation/pages/notification_page_liste.js.map +1 -1
  58. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js +2 -2
  59. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js.map +1 -1
  60. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js +2 -2
  61. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js.map +1 -1
  62. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js +156 -25
  63. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js.map +1 -1
  64. package/lib/module/modules/profile/presentation/pages/web/profile_web_page.js.map +1 -1
  65. package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page.js.map +1 -1
  66. package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js +2 -2
  67. package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js.map +1 -1
  68. package/lib/module/modules/search/domain/models/fonctionnalite.js +199 -0
  69. package/lib/module/modules/search/domain/models/fonctionnalite.js.map +1 -0
  70. package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js +119 -0
  71. package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js.map +1 -0
  72. package/lib/module/modules/search/presentation/pages/search_page.js +359 -0
  73. package/lib/module/modules/search/presentation/pages/search_page.js.map +1 -0
  74. package/lib/module/modules/search/presentation/pages/search_page_voice_button.js +335 -0
  75. package/lib/module/modules/search/presentation/pages/search_page_voice_button.js.map +1 -0
  76. package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js +2 -2
  77. package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js.map +1 -1
  78. package/lib/module/modules/transactions/domain/models/transaction_send/transaction_send_command.js +6 -2
  79. package/lib/module/modules/transactions/domain/models/transaction_send/transaction_send_command.js.map +1 -1
  80. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js +13 -1
  81. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js.map +1 -1
  82. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js +7 -1
  83. package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js.map +1 -1
  84. package/lib/module/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.js +3 -1
  85. package/lib/module/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.js.map +1 -1
  86. package/lib/module/modules/transactions/presentation/pages/transaction_jour_libelle.js +15 -0
  87. package/lib/module/modules/transactions/presentation/pages/transaction_jour_libelle.js.map +1 -0
  88. package/lib/module/modules/transactions/presentation/pages/transaction_list_item_widget.js +2 -1
  89. package/lib/module/modules/transactions/presentation/pages/transaction_list_item_widget.js.map +1 -1
  90. package/lib/module/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.js +5 -3
  91. package/lib/module/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.js.map +1 -1
  92. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js +216 -137
  93. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js.map +1 -1
  94. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js +8 -3
  95. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js.map +1 -1
  96. package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js +3 -3
  97. package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js.map +1 -1
  98. package/lib/module/shared/widgets/amount_widget.js +4 -1
  99. package/lib/module/shared/widgets/amount_widget.js.map +1 -1
  100. package/lib/module/shared/widgets/input_amount.js.map +1 -1
  101. package/lib/module/shared/widgets/input_text.js +2 -0
  102. package/lib/module/shared/widgets/input_text.js.map +1 -1
  103. package/lib/module/shared/widgets/menu_actions_widget.js +10 -1
  104. package/lib/module/shared/widgets/menu_actions_widget.js.map +1 -1
  105. package/lib/module/shared/widgets/pagination_loader_widget.js +29 -0
  106. package/lib/module/shared/widgets/pagination_loader_widget.js.map +1 -0
  107. package/lib/typescript/src/core/assets.d.ts +1 -0
  108. package/lib/typescript/src/core/assets.d.ts.map +1 -1
  109. package/lib/typescript/src/core/di.d.ts.map +1 -1
  110. package/lib/typescript/src/core/pi_spi_custom_theme.d.ts +49 -0
  111. package/lib/typescript/src/core/pi_spi_custom_theme.d.ts.map +1 -0
  112. package/lib/typescript/src/core/pi_spi_sdk_config.d.ts +15 -2
  113. package/lib/typescript/src/core/pi_spi_sdk_config.d.ts.map +1 -1
  114. package/lib/typescript/src/core/router.d.ts +1 -0
  115. package/lib/typescript/src/core/router.d.ts.map +1 -1
  116. package/lib/typescript/src/core/routes.d.ts.map +1 -1
  117. package/lib/typescript/src/core/storage.d.ts +11 -3
  118. package/lib/typescript/src/core/storage.d.ts.map +1 -1
  119. package/lib/typescript/src/core/theme.d.ts +19 -0
  120. package/lib/typescript/src/core/theme.d.ts.map +1 -1
  121. package/lib/typescript/src/index.d.ts +2 -1
  122. package/lib/typescript/src/index.d.ts.map +1 -1
  123. package/lib/typescript/src/l10n/app_localizations.d.ts +46 -0
  124. package/lib/typescript/src/l10n/app_localizations.d.ts.map +1 -1
  125. package/lib/typescript/src/l10n/app_localizations_en.d.ts.map +1 -1
  126. package/lib/typescript/src/l10n/app_localizations_fr.d.ts.map +1 -1
  127. package/lib/typescript/src/l10n/app_localizations_pt.d.ts.map +1 -1
  128. package/lib/typescript/src/modules/categorie/presentation/pages/categorie_select_widget.d.ts.map +1 -1
  129. package/lib/typescript/src/modules/compte/presentation/pages/solde_widget_with_qr.d.ts.map +1 -1
  130. package/lib/typescript/src/modules/home/presentation/pages/home_page.d.ts.map +1 -1
  131. package/lib/typescript/src/modules/introduction/presentation/pages/introduction_bar.d.ts.map +1 -1
  132. package/lib/typescript/src/modules/introduction/presentation/pages/introduction_page.d.ts.map +1 -1
  133. package/lib/typescript/src/modules/notification/presentation/bloc/notification_bloc.d.ts.map +1 -1
  134. package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts +5 -0
  135. package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts.map +1 -1
  136. package/lib/typescript/src/modules/notification/presentation/pages/notification_page_liste.d.ts.map +1 -1
  137. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page.d.ts.map +1 -1
  138. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.d.ts.map +1 -1
  139. package/lib/typescript/src/modules/profile/presentation/pages/web/profile_web_page.d.ts.map +1 -1
  140. package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts +70 -0
  141. package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts.map +1 -0
  142. package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts +21 -0
  143. package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts.map +1 -0
  144. package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts +21 -0
  145. package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts.map +1 -0
  146. package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts +29 -0
  147. package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts.map +1 -0
  148. package/lib/typescript/src/modules/transactions/domain/models/transaction_send/transaction_send_command.d.ts +1 -1
  149. package/lib/typescript/src/modules/transactions/domain/models/transaction_send/transaction_send_command.d.ts.map +1 -1
  150. package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.d.ts.map +1 -1
  151. package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts +5 -0
  152. package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts.map +1 -1
  153. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.d.ts +2 -1
  154. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.d.ts.map +1 -1
  155. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_jour_libelle.d.ts +4 -0
  156. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_jour_libelle.d.ts.map +1 -0
  157. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_list_item_widget.d.ts +2 -1
  158. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_list_item_widget.d.ts.map +1 -1
  159. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.d.ts.map +1 -1
  160. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts +5 -1
  161. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts.map +1 -1
  162. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.d.ts.map +1 -1
  163. package/lib/typescript/src/shared/widgets/amount_widget.d.ts +3 -1
  164. package/lib/typescript/src/shared/widgets/amount_widget.d.ts.map +1 -1
  165. package/lib/typescript/src/shared/widgets/input_text.d.ts +3 -3
  166. package/lib/typescript/src/shared/widgets/input_text.d.ts.map +1 -1
  167. package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts +5 -1
  168. package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts.map +1 -1
  169. package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts +7 -0
  170. package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts.map +1 -0
  171. package/package.json +49 -35
  172. package/src/core/assets.ts +1 -0
  173. package/src/core/di.ts +8 -1
  174. package/src/core/pi_spi_custom_theme.ts +66 -0
  175. package/src/core/pi_spi_sdk_config.ts +18 -3
  176. package/src/core/router.tsx +3 -0
  177. package/src/core/routes.tsx +15 -0
  178. package/src/core/storage.ts +30 -22
  179. package/src/core/theme.ts +53 -0
  180. package/src/index.ts +2 -1
  181. package/src/l10n/app_localizations.ts +46 -0
  182. package/src/l10n/app_localizations_en.ts +23 -0
  183. package/src/l10n/app_localizations_fr.ts +23 -0
  184. package/src/l10n/app_localizations_pt.ts +23 -0
  185. package/src/modules/alias/presentation/pages/alias_page_create_options.tsx +3 -3
  186. package/src/modules/alias/presentation/pages/alias_page_otp.tsx +2 -2
  187. package/src/modules/categorie/presentation/pages/categorie_edit_icon_btn.tsx +2 -2
  188. package/src/modules/categorie/presentation/pages/categorie_select_widget.tsx +13 -9
  189. package/src/modules/compte/presentation/pages/solde_widget_with_qr.tsx +10 -3
  190. package/src/modules/home/presentation/pages/home_page.tsx +31 -2
  191. package/src/modules/home/presentation/pages/home_tab_compte.tsx +1 -1
  192. package/src/modules/home/presentation/pages/home_tab_compte_more_menu.tsx +2 -2
  193. package/src/modules/introduction/presentation/pages/introduction_bar.tsx +12 -2
  194. package/src/modules/introduction/presentation/pages/introduction_page.tsx +20 -2
  195. package/src/modules/notification/presentation/bloc/notification_bloc.ts +12 -3
  196. package/src/modules/notification/presentation/bloc/notification_state.ts +11 -1
  197. package/src/modules/notification/presentation/pages/notification_page_liste.tsx +4 -1
  198. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page.tsx +5 -2
  199. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx +2 -2
  200. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.tsx +95 -15
  201. package/src/modules/profile/presentation/pages/web/profile_web_page.tsx +3 -2
  202. package/src/modules/qrcode/presentation/pages/qrcode_alias_page.tsx +2 -2
  203. package/src/modules/qrcode/presentation/pages/qrcode_alias_page_share.tsx +2 -2
  204. package/src/modules/search/domain/models/fonctionnalite.ts +217 -0
  205. package/src/modules/search/presentation/pages/search_fonctionnalites.ts +135 -0
  206. package/src/modules/search/presentation/pages/search_page.tsx +314 -0
  207. package/src/modules/search/presentation/pages/search_page_voice_button.tsx +283 -0
  208. package/src/modules/subscription/presentation/pages/subscription_menu_widget.tsx +2 -2
  209. package/src/modules/transactions/domain/models/transaction_send/transaction_send_command.ts +6 -2
  210. package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.ts +15 -1
  211. package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.ts +10 -1
  212. package/src/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.tsx +3 -0
  213. package/src/modules/transactions/presentation/pages/transaction_jour_libelle.ts +17 -0
  214. package/src/modules/transactions/presentation/pages/transaction_list_item_widget.tsx +3 -1
  215. package/src/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.tsx +7 -4
  216. package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.tsx +204 -101
  217. package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.tsx +6 -2
  218. package/src/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.tsx +3 -3
  219. package/src/shared/widgets/amount_widget.tsx +5 -2
  220. package/src/shared/widgets/input_amount.tsx +2 -2
  221. package/src/shared/widgets/input_text.tsx +8 -3
  222. package/src/shared/widgets/menu_actions_widget.tsx +8 -2
  223. package/src/shared/widgets/pagination_loader_widget.tsx +24 -0
@@ -1,80 +1,154 @@
1
1
  import { MaterialIcons } from '@expo/vector-icons';
2
- import { useReducer } from 'react';
3
- import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
2
+ import { useState } from 'react';
3
+ import { Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
4
4
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
5
 
6
- import { useBloc, useBlocBuilder } from '../../../../../core/bloc_react';
6
+ import { useBloc } from '../../../../../core/bloc_react';
7
7
  import { AppRouter, useAppRouter } from '../../../../../core/router';
8
8
  import { Themer, useTheme } from '../../../../../core/theme';
9
9
  import type { AppLocalizations } from '../../../../../l10n/app_localizations';
10
10
  import { useAppLocalizations } from '../../../../../l10n/localizations';
11
- import { TextButton } from '../../../../../shared/material/buttons';
11
+ import { ElevatedButton, TextButton } from '../../../../../shared/material/buttons';
12
12
  import { Card } from '../../../../../shared/material/card';
13
13
  import { DateFormat } from '../../../../../shared/utils/date_format';
14
14
  import { showSelectDateRange } from '../../../../../shared/widgets/select_date_widget';
15
15
  import { CategorieSelectWidget } from '../../../../categorie/presentation/pages/categorie_select_widget';
16
16
  import { TransactionSens } from '../../../domain/models/transaction';
17
- import type { TransactionSearchCommand } from '../../../domain/models/transaction_search/transaction_search_command';
18
17
  import { TransactionSearchBloc } from '../../bloc/transaction_search/transaction_search_bloc';
19
18
 
20
- /** Filtres de la recherche : plage de dates, sens (reçus / payés) et catégories. */
19
+ /** Sens par défaut : reçus et envoyés */
20
+ const SENS_PAR_DEFAUT: readonly TransactionSens[] = [TransactionSens.credit, TransactionSens.debit];
21
+
22
+ /**
23
+ * Filtres de la recherche : plage de dates, sens (reçus / envoyés) et
24
+ * catégories. Les choix sont faits sur un brouillon : ils ne sont reportés
25
+ * sur la commande qu'avec « Appliquer ». Fermer la page les abandonne.
26
+ */
21
27
  export function TransactionSearchPageFilters() {
22
28
  const traductions = useAppLocalizations();
23
29
  const theme = useTheme();
24
30
  const router = useAppRouter();
25
31
  const insets = useSafeAreaInsets();
26
- const bloc = useBloc(TransactionSearchBloc);
27
- const state = useBlocBuilder(bloc);
28
- const command = state.command;
29
- const [, rebuild] = useReducer((x: number) => x + 1, 0);
32
+ const command = useBloc(TransactionSearchBloc).state.command;
33
+
34
+ // Brouillon des filtres
35
+ const [dateDebut, setDateDebut] = useState(command.filters.dateDebut);
36
+ const [dateFin, setDateFin] = useState(command.filters.dateFin);
37
+ const [sens, setSens] = useState<TransactionSens[]>(() => [...command.filters.sens]);
38
+ const [categories, setCategories] = useState<string[]>(() => [...command.filters.categories]);
39
+ // Change à chaque réinitialisation : les listes de catégories gardent leur
40
+ // sélection en interne, il faut les recréer pour la vider.
41
+ const [versionCategories, setVersionCategories] = useState(0);
42
+
43
+ const estModifie =
44
+ dateDebut != null ||
45
+ dateFin != null ||
46
+ categories.length > 0 ||
47
+ sens.length !== SENS_PAR_DEFAUT.length ||
48
+ !SENS_PAR_DEFAUT.every((s) => sens.includes(s));
49
+
50
+ const reinitialiser = () => {
51
+ setDateDebut(undefined);
52
+ setDateFin(undefined);
53
+ setSens([...SENS_PAR_DEFAUT]);
54
+ setCategories([]);
55
+ setVersionCategories((v) => v + 1);
56
+ };
57
+
58
+ const appliquer = () => {
59
+ command.filters.dateDebut = dateDebut;
60
+ command.filters.dateFin = dateFin;
61
+ command.filters.sens = [...sens];
62
+ command.filters.categories = [...categories];
63
+ AppRouter.pop(router, { value: command });
64
+ };
65
+
66
+ const separateurColor = theme.brightness === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';
30
67
 
31
68
  return (
32
69
  <View style={[styles.flex, { paddingBottom: insets.bottom }]}>
33
- {/* Barre : retour et « Appliquer » */}
70
+ {/* Barre : fermer, titre, réinitialiser */}
34
71
  <View style={styles.bar}>
35
- <Pressable accessibilityRole="button" hitSlop={12} style={styles.back} onPress={() => AppRouter.pop(router)}>
36
- <MaterialIcons name="arrow-back" size={24} color={theme.colorScheme.onSurface} />
72
+ <Pressable accessibilityRole="button" hitSlop={12} style={styles.close} onPress={() => AppRouter.pop(router)}>
73
+ <MaterialIcons name="close" size={24} color={theme.colorScheme.onSurface} />
37
74
  </Pressable>
38
- <View style={styles.apply}>
39
- <TextButton onPress={() => AppRouter.pop(router, { value: command })}>{traductions.transactionSearchInputFilterBtnAppliquer}</TextButton>
40
- </View>
75
+ <Text style={[theme.textTheme.headlineMedium, styles.title]}>{traductions.transactionSearchInputFilterTitle}</Text>
76
+ <TextButton onPress={estModifie ? reinitialiser : undefined}>
77
+ {traductions.transactionSearchInputFilterBtnReinitialiser}
78
+ </TextButton>
41
79
  </View>
42
- <View style={styles.content}>
43
- <View style={styles.gap10} />
44
- <Text style={theme.textTheme.titleSmall}>{traductions.transactionSearchInputFilterTitle}</Text>
45
- <View style={styles.gap20} />
46
- <ScrollView style={styles.flex}>
47
- {/* Plage de dates */}
48
- <Text style={theme.textTheme.headlineSmall}>{traductions.transactionSearchInputFilterDateTitle}</Text>
49
- <View style={styles.gap14} />
50
- <FiltersDates command={command} traductions={traductions} onChange={rebuild} />
51
- <View style={styles.gap20} />
52
- {/* Catégories */}
53
- <Text style={theme.textTheme.headlineSmall}>{traductions.transactionSearchInputFilterCategoriesTitle}</Text>
54
- <View style={styles.gap14} />
55
- {/* Selon le sens de l'opération */}
56
- <FiltersSens command={command} traductions={traductions} onChange={rebuild} />
57
- <View style={styles.gap20} />
58
- {/* Selon les catégories de transactions */}
59
- <CategorieSelectWidget
60
- selectedCategories={command.filters.categories}
61
- onSelectionChanged={(categorie, checked) => {
62
- if (checked) command.filters.categories.push(categorie.id);
63
- else command.filters.categories = command.filters.categories.filter((c) => c !== categorie.id);
64
- }}
65
- />
66
- <View style={styles.gap20} />
67
- </ScrollView>
80
+ {/* Séparateur discret */}
81
+ <View style={[styles.separateur, { backgroundColor: separateurColor }]} />
82
+
83
+ {/* Filtres */}
84
+ <ScrollView style={styles.flex} contentContainerStyle={styles.content}>
85
+ <SectionTitle label={traductions.transactionSearchInputFilterDateTitle} />
86
+ <FiltersDates
87
+ dateDebut={dateDebut}
88
+ dateFin={dateFin}
89
+ traductions={traductions}
90
+ onChange={(debut, fin) => {
91
+ setDateDebut(debut);
92
+ setDateFin(fin);
93
+ }}
94
+ />
95
+ <View style={styles.gap28} />
96
+ {/* Sens de l'opération, puis catégories */}
97
+ <SectionTitle label={traductions.transactionSearchInputFilterCategoriesTitle} />
98
+ <FiltersSens selection={sens} traductions={traductions} separateurColor={separateurColor} onChange={setSens} />
99
+ <View style={styles.gap16} />
100
+ <CategorieSelectWidget
101
+ key={versionCategories}
102
+ selectedCategories={categories}
103
+ onSelectionChanged={(categorie, checked) => {
104
+ setCategories((c) => {
105
+ const autres = c.filter((id) => id !== categorie.id);
106
+ return checked ? [...autres, categorie.id] : autres;
107
+ });
108
+ }}
109
+ />
110
+ </ScrollView>
111
+
112
+ {/* Appliquer */}
113
+ <View style={styles.footer}>
114
+ <ElevatedButton onPress={appliquer}>{traductions.transactionSearchInputFilterBtnAppliquer}</ElevatedButton>
68
115
  </View>
69
116
  </View>
70
117
  );
71
118
  }
72
119
 
73
- function FiltersDates({ command, traductions, onChange }: { command: TransactionSearchCommand; traductions: AppLocalizations; onChange: () => void }) {
120
+ /** Titre d'une section de filtres */
121
+ function SectionTitle({ label }: { label: string }) {
122
+ const theme = useTheme();
123
+ return <Text style={[theme.textTheme.displayMedium, styles.sectionTitle]}>{label.toUpperCase()}</Text>;
124
+ }
125
+
126
+ /** Icône dans un carré arrondi */
127
+ function IconBox({ name, color, backgroundColor }: { name: keyof typeof MaterialIcons.glyphMap; color: string; backgroundColor: string }) {
128
+ return (
129
+ <View style={[styles.icon, { backgroundColor }]}>
130
+ <MaterialIcons name={name} size={22} color={color} />
131
+ </View>
132
+ );
133
+ }
134
+
135
+ function FiltersDates({
136
+ dateDebut,
137
+ dateFin,
138
+ traductions,
139
+ onChange,
140
+ }: {
141
+ dateDebut?: Date;
142
+ dateFin?: Date;
143
+ traductions: AppLocalizations;
144
+ onChange: (debut?: Date, fin?: Date) => void;
145
+ }) {
74
146
  const theme = useTheme();
75
147
  const router = useAppRouter();
76
- const format = new DateFormat('d MMM', traductions.localeName);
77
- const { dateDebut, dateFin } = command.filters;
148
+ const format = new DateFormat('d MMM y', traductions.localeName);
149
+ const estDefinie = dateDebut != null;
150
+ const plage = estDefinie ? (dateFin != null ? `${format.format(dateDebut)} – ${format.format(dateFin)}` : format.format(dateDebut)) : null;
151
+
78
152
  return (
79
153
  <Card>
80
154
  <Pressable
@@ -82,64 +156,90 @@ function FiltersDates({ command, traductions, onChange }: { command: Transaction
82
156
  style={styles.tile}
83
157
  onPress={async () => {
84
158
  const dates = await showSelectDateRange(router, { title: traductions.transactionSearchInputFilterDateSelectTitle });
85
- command.filters.dateDebut = dates[0];
86
- command.filters.dateFin = dates.length === 2 ? dates[1] : undefined;
87
- onChange();
159
+ onChange(dates[0], dates.length === 2 ? dates[1] : undefined);
88
160
  }}
89
161
  >
90
- <View style={[styles.icon, { backgroundColor: theme.colorScheme.surface }]}>
91
- <MaterialIcons name="calendar-month" size={24} color={theme.colorScheme.onSurface} />
92
- </View>
93
- <View style={styles.texts}>
94
- {/* Indication : sélectionner la plage */}
95
- <Text style={theme.textTheme.headlineSmall}>{traductions.transactionSearchInputFilterDateSubTitle}</Text>
96
- {/* Plage choisie */}
97
- <Text style={theme.textTheme.displaySmall}>
98
- {traductions.transactionSearchInputFilterDateRange(
99
- dateDebut != null ? format.format(dateDebut) : '',
100
- dateFin != null ? format.format(dateFin) : '',
101
- )}
102
- </Text>
103
- </View>
104
- <MaterialIcons name="arrow-forward-ios" size={18} color={theme.colorScheme.onSurface} />
162
+ {/* Teinté quand le filtre est actif */}
163
+ <IconBox
164
+ name="calendar-month"
165
+ color={estDefinie ? Themer.primaryDark : Themer.neural04Color}
166
+ backgroundColor={estDefinie ? 'rgba(242,169,0,0.15)' : theme.colorScheme.surface}
167
+ />
168
+ {/* La plage choisie, sinon l'invitation à la choisir */}
169
+ <Text style={[theme.textTheme.headlineSmall, styles.label15, styles.texts]}>
170
+ {plage ?? traductions.transactionSearchInputFilterDateSubTitle}
171
+ </Text>
172
+ {/* Effacer la plage, ou ouvrir le sélecteur */}
173
+ {estDefinie ? (
174
+ <Pressable accessibilityRole="button" hitSlop={8} style={styles.trailing} onPress={() => onChange(undefined, undefined)}>
175
+ <MaterialIcons name="close" size={20} color={Themer.neural03Color} />
176
+ </Pressable>
177
+ ) : (
178
+ <MaterialIcons
179
+ style={styles.trailing}
180
+ name={Platform.OS === 'ios' ? 'arrow-forward-ios' : 'arrow-forward'}
181
+ size={Platform.OS === 'ios' ? 18 : 22}
182
+ color={Themer.neural03Color}
183
+ />
184
+ )}
105
185
  </Pressable>
106
186
  </Card>
107
187
  );
108
188
  }
109
189
 
110
- function FiltersSens({ command, traductions, onChange }: { command: TransactionSearchCommand; traductions: AppLocalizations; onChange: () => void }) {
190
+ /** Mêmes icônes et couleurs que dans la liste des transactions. */
191
+ function FiltersSens({
192
+ selection,
193
+ traductions,
194
+ separateurColor,
195
+ onChange,
196
+ }: {
197
+ selection: TransactionSens[];
198
+ traductions: AppLocalizations;
199
+ separateurColor: string;
200
+ onChange: (sens: TransactionSens[]) => void;
201
+ }) {
111
202
  const theme = useTheme();
112
203
  const items = [
113
- { sens: TransactionSens.credit, icon: 'arrow-forward' as const, label: traductions.transactionSearchInputFilterCategoriesSensRecus },
114
- { sens: TransactionSens.debit, icon: 'arrow-back' as const, label: traductions.transactionSearchInputFilterCategoriesSensPayes },
204
+ {
205
+ sens: TransactionSens.credit,
206
+ icon: 'arrow-forward' as const,
207
+ color: Themer.successColor,
208
+ fond: 'rgba(70,169,61,0.12)',
209
+ label: traductions.transactionSearchInputFilterCategoriesSensRecus,
210
+ },
211
+ {
212
+ sens: TransactionSens.debit,
213
+ icon: 'arrow-back' as const,
214
+ color: theme.colorScheme.error,
215
+ fond: 'rgba(250,90,37,0.12)',
216
+ label: traductions.transactionSearchInputFilterCategoriesSensPayes,
217
+ },
115
218
  ];
116
219
  return (
117
220
  <Card>
118
- {items.map((item) => {
119
- const checked = command.filters.sens.includes(item.sens);
221
+ {items.map((item, index) => {
222
+ const checked = selection.includes(item.sens);
120
223
  return (
121
- <Pressable
122
- key={item.sens}
123
- accessibilityRole="checkbox"
124
- accessibilityState={{ checked }}
125
- style={styles.tile}
126
- onPress={() => {
127
- if (checked) command.filters.sens = command.filters.sens.filter((s) => s !== item.sens);
128
- else command.filters.sens.push(item.sens);
129
- onChange();
130
- }}
131
- >
132
- <MaterialIcons
133
- name={checked ? 'check-box' : 'check-box-outline-blank'}
134
- size={24}
135
- color={checked ? theme.colorScheme.primary : Themer.neural02Color}
136
- />
137
- <View style={styles.gap16} />
138
- <View style={[styles.icon, { backgroundColor: theme.colorScheme.surface }]}>
139
- <MaterialIcons name={item.icon} size={24} color={theme.colorScheme.onSurface} />
140
- </View>
141
- <Text style={[theme.textTheme.headlineSmall, styles.label]}>{item.label}</Text>
142
- </Pressable>
224
+ <View key={item.sens}>
225
+ {/* Séparateur discret */}
226
+ {index > 0 && <View style={[styles.separateur, styles.separateurIndent, { backgroundColor: separateurColor }]} />}
227
+ <Pressable
228
+ accessibilityRole="checkbox"
229
+ accessibilityState={{ checked }}
230
+ style={styles.tile}
231
+ onPress={() => onChange(checked ? selection.filter((s) => s !== item.sens) : [...selection, item.sens])}
232
+ >
233
+ <IconBox name={item.icon} color={item.color} backgroundColor={item.fond} />
234
+ <Text style={[theme.textTheme.headlineSmall, styles.label15, styles.texts]}>{item.label}</Text>
235
+ <MaterialIcons
236
+ style={styles.trailing}
237
+ name={checked ? 'check-box' : 'check-box-outline-blank'}
238
+ size={24}
239
+ color={checked ? theme.colorScheme.primary : Themer.neural02Color}
240
+ />
241
+ </Pressable>
242
+ </View>
143
243
  );
144
244
  })}
145
245
  </Card>
@@ -148,16 +248,19 @@ function FiltersSens({ command, traductions, onChange }: { command: TransactionS
148
248
 
149
249
  const styles = StyleSheet.create({
150
250
  flex: { flex: 1 },
151
- bar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
152
- back: { padding: 12 },
153
- apply: { paddingRight: 12 },
154
- content: { flex: 1, paddingTop: 12, paddingHorizontal: 12 },
155
- tile: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 8 },
156
- icon: { width: 45, height: 45, borderRadius: 14, alignItems: 'center', justifyContent: 'center' },
251
+ bar: { flexDirection: 'row', alignItems: 'center', paddingTop: 8, paddingLeft: 4, paddingRight: 8 },
252
+ close: { padding: 12 },
253
+ title: { flex: 1, textAlign: 'center' },
254
+ separateur: { height: StyleSheet.hairlineWidth },
255
+ separateurIndent: { marginLeft: 76, marginRight: 16 },
256
+ content: { paddingHorizontal: 16, paddingTop: 24, paddingBottom: 16 },
257
+ sectionTitle: { color: Themer.neural03Color, letterSpacing: 0.6, marginLeft: 4, marginBottom: 10 },
258
+ tile: { flexDirection: 'row', alignItems: 'center', paddingLeft: 16, paddingRight: 8, paddingVertical: 10 },
259
+ icon: { width: 44, height: 44, borderRadius: 14, alignItems: 'center', justifyContent: 'center' },
157
260
  texts: { flex: 1, marginHorizontal: 16 },
158
- label: { marginLeft: 12 },
159
- gap10: { height: 10 },
160
- gap14: { height: 14 },
161
- gap16: { width: 16 },
162
- gap20: { height: 20 },
261
+ label15: { fontSize: 15 },
262
+ trailing: { marginRight: 8 },
263
+ footer: { paddingHorizontal: 16, paddingTop: 8, paddingBottom: 16 },
264
+ gap16: { height: 16 },
265
+ gap28: { height: 28 },
163
266
  });
@@ -5,12 +5,13 @@ import { useBloc, useBlocBuilder } from '../../../../../core/bloc_react';
5
5
  import { Themer, useTheme } from '../../../../../core/theme';
6
6
  import { useAppLocalizations } from '../../../../../l10n/localizations';
7
7
  import { Card } from '../../../../../shared/material/card';
8
- import { DateFormat } from '../../../../../shared/utils/date_format';
9
8
  import { AmountWidget } from '../../../../../shared/widgets/amount_widget';
9
+ import { PaginationLoaderWidget } from '../../../../../shared/widgets/pagination_loader_widget';
10
10
  import { TransactionSens, type Transaction } from '../../../domain/models/transaction';
11
11
  import { TransactionSearchBloc } from '../../bloc/transaction_search/transaction_search_bloc';
12
12
  import { TransactionSearchListEvent, TransactionSearchPaginateEvent } from '../../bloc/transaction_search/transaction_search_event';
13
13
  import { TransactionSearchListState } from '../../bloc/transaction_search/transaction_search_state';
14
+ import { transactionJourLibelle } from '../transaction_jour_libelle';
14
15
  import { TransactionListItemWidget } from '../transaction_list_item_widget';
15
16
  import { TransactionListLoadingWidget } from '../transaction_list_loading_widget';
16
17
 
@@ -89,11 +90,13 @@ export function TransactionSearchPageListe({
89
90
  if (command.canLoadMore()) bloc.add(new TransactionSearchPaginateEvent(command.index, command));
90
91
  }}
91
92
  onEndReachedThreshold={0.2}
93
+ // Chargement de la page suivante
94
+ ListFooterComponent={state.isPaginating ? <PaginationLoaderWidget /> : undefined}
92
95
  renderItem={({ item }) => (
93
96
  <View>
94
97
  {/* Jour et bilan */}
95
98
  <View style={styles.header}>
96
- <Text style={headerStyle}>{new DateFormat('d MMMM', traductions.localeName).format(new Date(`${item.jour}T00:00:00`))}</Text>
99
+ <Text style={headerStyle}>{transactionJourLibelle(traductions, item.jour)}</Text>
97
100
  <AmountWidget montant={item.bilanOperation} style={headerStyle} />
98
101
  </View>
99
102
  <View style={styles.gap8} />
@@ -104,6 +107,7 @@ export function TransactionSearchPageListe({
104
107
  <TransactionListItemWidget
105
108
  key={transaction.endToEndId}
106
109
  transaction={transaction}
110
+ datePartern="HH:mm"
107
111
  onSelect={select != null ? (value: boolean) => select(transaction, value) : undefined}
108
112
  isSelected={selectedItems?.some((t) => t.endToEndId === transaction.endToEndId) ?? false}
109
113
  />
@@ -4,7 +4,7 @@ import { Images } from '../../../../../core/assets';
4
4
  import { useBloc } from '../../../../../core/bloc_react';
5
5
  import { AppRouter, useAppRouter } from '../../../../../core/router';
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 { AliasBloc } from '../../../../alias/presentation/bloc/alias_bloc';
9
9
  import { ContactPI } from '../../../../contacts/domain/contact_pi';
10
10
  import { TransactionCanal } from '../../../domain/models/transaction_canal';
@@ -66,12 +66,12 @@ export function TransactionSendPageOptions({ action }: { action: string }) {
66
66
 
67
67
  return (
68
68
  <View>
69
- <MenuActionsWiget items={items} />
69
+ <MenuActionsWidget items={items} />
70
70
  {Platform.OS !== 'web' && (
71
71
  <>
72
72
  <View style={{ height: 5 }} />
73
73
  {/* Nouveau contact */}
74
- <MenuActionsWiget
74
+ <MenuActionsWidget
75
75
  items={[
76
76
  new MenuActionItem(
77
77
  Images.iconTransactionSendByNewContact,
@@ -22,6 +22,7 @@ export function AmountWidget({
22
22
  surfixText,
23
23
  style,
24
24
  numberOfLines,
25
+ adjustsFontSizeToFit,
25
26
  }: {
26
27
  montant: number | null | undefined;
27
28
  sign?: string;
@@ -29,6 +30,8 @@ export function AmountWidget({
29
30
  surfixText?: string;
30
31
  style?: TextStyle;
31
32
  numberOfLines?: number;
33
+ /** Réduit la police pour que le texte tienne dans la largeur disponible (avec [numberOfLines]). */
34
+ adjustsFontSizeToFit?: boolean;
32
35
  }) {
33
36
  const locale = useAppLocalizations().localeName;
34
37
  const configBloc = useBloc(ConfigBloc);
@@ -45,7 +48,7 @@ export function AmountWidget({
45
48
  const text = `${prefix}${value}${suffix}`;
46
49
  if (displayAmount) {
47
50
  return (
48
- <Text numberOfLines={numberOfLines} style={style}>
51
+ <Text numberOfLines={numberOfLines} adjustsFontSizeToFit={adjustsFontSizeToFit} style={style}>
49
52
  {text}
50
53
  </Text>
51
54
  );
@@ -56,7 +59,7 @@ export function AmountWidget({
56
59
  // transparent, d'où le flou porté par la vue.
57
60
  return (
58
61
  <View style={styles.hidden}>
59
- <Text numberOfLines={numberOfLines} style={[style, styles.transparent]}>
62
+ <Text numberOfLines={numberOfLines} adjustsFontSizeToFit={adjustsFontSizeToFit} style={[style, styles.transparent]}>
60
63
  {text}
61
64
  </Text>
62
65
  </View>
@@ -1,5 +1,5 @@
1
1
  import { useRef, useState, type ReactNode } from 'react';
2
- import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
2
+ import { Pressable, StyleSheet, Text, TextInput, View, type TextInputInstance } from 'react-native';
3
3
 
4
4
  import { Themer, useTheme } from '../../core/theme';
5
5
  import { useAppLocalizations } from '../../l10n/localizations';
@@ -60,7 +60,7 @@ export function InputAmount({
60
60
  const traductions = useAppLocalizations();
61
61
  const locale = traductions.localeName;
62
62
  const input = theme.inputDecorationTheme;
63
- const ref = useRef<TextInput>(null);
63
+ const ref = useRef<TextInputInstance>(null);
64
64
  const [focused, setFocused] = useState(false);
65
65
  const [text, setText] = useState(() => formatSaisie(value ?? '', locale));
66
66
  const hasError = messageError != null && messageError.length > 0;
@@ -1,5 +1,5 @@
1
- import { forwardRef, useRef, useState, type ReactNode } from 'react';
2
- import { StyleSheet, Text, TextInput, View, type KeyboardTypeOptions, type ReturnKeyTypeOptions } from 'react-native';
1
+ import { forwardRef, useRef, useState, type ForwardRefExoticComponent, type ReactNode, type RefAttributes } from 'react';
2
+ import { StyleSheet, Text, TextInput, View, type KeyboardTypeOptions, type ReturnKeyTypeOptions, type TextInputInstance } from 'react-native';
3
3
 
4
4
  import { useTheme } from '../../core/theme';
5
5
  import { FloatingLabel } from './floating_label';
@@ -37,7 +37,12 @@ const HEIGHT = 56;
37
37
  * comme un `TextField` Material (le texte d'aide n'apparaît qu'au focus
38
38
  * quand il y a un label), et message d'erreur sous le champ.
39
39
  */
40
- export const CustomTextInput = forwardRef<TextInput, CustomTextInputProps>(function CustomTextInput(props, ref) {
40
+ // Type annoté : celui inféré depuis React Native 0.88 n'est pas exportable
41
+ // dans les déclarations du package.
42
+ export const CustomTextInput: ForwardRefExoticComponent<CustomTextInputProps & RefAttributes<TextInputInstance>> = forwardRef<
43
+ TextInputInstance,
44
+ CustomTextInputProps
45
+ >(function CustomTextInput(props, ref) {
41
46
  const theme = useTheme();
42
47
  const input = theme.inputDecorationTheme;
43
48
  const [focused, setFocused] = useState(false);
@@ -1,4 +1,5 @@
1
1
  import { Image } from 'expo-image';
2
+ import type { ReactNode } from 'react';
2
3
  import { Pressable, StyleSheet, Text, View } from 'react-native';
3
4
 
4
5
  import { useTheme } from '../../core/theme';
@@ -7,21 +8,24 @@ import { Card } from '../material/card';
7
8
  export class MenuActionItem {
8
9
  readonly iconSize?: number;
9
10
  readonly color?: string;
11
+ /** Élément affiché à droite de la ligne (ex. une flèche). */
12
+ readonly trailing?: ReactNode;
10
13
 
11
14
  constructor(
12
15
  readonly icon: number,
13
16
  readonly title: string,
14
17
  readonly subtitle: string | null | undefined,
15
18
  readonly action: () => void,
16
- options: { iconSize?: number; color?: string } = {},
19
+ options: { iconSize?: number; color?: string; trailing?: ReactNode } = {},
17
20
  ) {
18
21
  this.iconSize = options.iconSize;
19
22
  this.color = options.color;
23
+ this.trailing = options.trailing;
20
24
  }
21
25
  }
22
26
 
23
27
  /** Liste d'actions (icône, titre, sous-titre) dans une carte, comme des `ListTile` Material. */
24
- export function MenuActionsWiget({ items }: { items: MenuActionItem[] }) {
28
+ export function MenuActionsWidget({ items }: { items: MenuActionItem[] }) {
25
29
  const theme = useTheme();
26
30
  return (
27
31
  <Card>
@@ -45,6 +49,7 @@ export function MenuActionsWiget({ items }: { items: MenuActionItem[] }) {
45
49
  <Text style={[theme.textTheme.headlineSmall, item.color ? { color: item.color } : null]}>{item.title}</Text>
46
50
  {item.subtitle != null && <Text style={theme.textTheme.displaySmall}>{item.subtitle}</Text>}
47
51
  </View>
52
+ {item.trailing != null && <View style={styles.trailing}>{item.trailing}</View>}
48
53
  </Pressable>
49
54
  ))}
50
55
  </Card>
@@ -55,4 +60,5 @@ const styles = StyleSheet.create({
55
60
  tile: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 8, minHeight: 72 },
56
61
  leading: { width: 45, height: 45, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginRight: 16 },
57
62
  texts: { flex: 1 },
63
+ trailing: { marginLeft: 16 },
58
64
  });
@@ -0,0 +1,24 @@
1
+ import { ActivityIndicator, Platform, StyleSheet, View } from 'react-native';
2
+
3
+ import { useTheme } from '../../core/theme';
4
+
5
+ /**
6
+ * Petit loader affiché en bas d'une liste pendant le chargement de la page
7
+ * suivante (pagination). Sur iOS, le loader natif garde sa couleur
8
+ * système ; sur le web et Android, il prend la couleur primaire.
9
+ */
10
+ export function PaginationLoaderWidget() {
11
+ const theme = useTheme();
12
+ return (
13
+ <View style={styles.container}>
14
+ <ActivityIndicator
15
+ size="small"
16
+ color={Platform.OS === 'ios' ? undefined : theme.colorScheme.primary}
17
+ />
18
+ </View>
19
+ );
20
+ }
21
+
22
+ const styles = StyleSheet.create({
23
+ container: { paddingVertical: 16, alignItems: 'center', justifyContent: 'center' },
24
+ });