@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.
- package/README.md +74 -7
- package/assets/icons/qr.png +0 -0
- package/assets/sounds/voice/search_prompt_en.mp3 +0 -0
- package/assets/sounds/voice/search_prompt_fr.mp3 +0 -0
- package/assets/sounds/voice/search_prompt_pt.mp3 +0 -0
- package/example/App.tsx +16 -1
- package/example/app.json +9 -1
- package/lib/module/core/assets.js +1 -0
- package/lib/module/core/assets.js.map +1 -1
- package/lib/module/core/di.js +8 -1
- package/lib/module/core/di.js.map +1 -1
- package/lib/module/core/pi_spi_custom_theme.js +63 -0
- package/lib/module/core/pi_spi_custom_theme.js.map +1 -0
- package/lib/module/core/pi_spi_sdk_config.js +15 -3
- package/lib/module/core/pi_spi_sdk_config.js.map +1 -1
- package/lib/module/core/router.js +3 -0
- package/lib/module/core/router.js.map +1 -1
- package/lib/module/core/routes.js +14 -0
- package/lib/module/core/routes.js.map +1 -1
- package/lib/module/core/storage.js +27 -29
- package/lib/module/core/storage.js.map +1 -1
- package/lib/module/core/theme.js +51 -0
- package/lib/module/core/theme.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/l10n/app_localizations.js.map +1 -1
- package/lib/module/l10n/app_localizations_en.js +24 -1
- package/lib/module/l10n/app_localizations_en.js.map +1 -1
- package/lib/module/l10n/app_localizations_fr.js +24 -1
- package/lib/module/l10n/app_localizations_fr.js.map +1 -1
- package/lib/module/l10n/app_localizations_pt.js +24 -1
- package/lib/module/l10n/app_localizations_pt.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js +3 -3
- package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_page_otp.js.map +1 -1
- package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js +2 -2
- package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js.map +1 -1
- package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js +12 -10
- package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js.map +1 -1
- package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js +14 -9
- package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js.map +1 -1
- package/lib/module/modules/home/presentation/pages/home_page.js +41 -2
- package/lib/module/modules/home/presentation/pages/home_page.js.map +1 -1
- package/lib/module/modules/home/presentation/pages/home_tab_compte.js +1 -2
- package/lib/module/modules/home/presentation/pages/home_tab_compte.js.map +1 -1
- package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js +2 -2
- package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js.map +1 -1
- package/lib/module/modules/introduction/presentation/pages/introduction_bar.js +8 -1
- package/lib/module/modules/introduction/presentation/pages/introduction_bar.js.map +1 -1
- package/lib/module/modules/introduction/presentation/pages/introduction_page.js +29 -4
- package/lib/module/modules/introduction/presentation/pages/introduction_page.js.map +1 -1
- package/lib/module/modules/notification/presentation/bloc/notification_bloc.js +11 -4
- package/lib/module/modules/notification/presentation/bloc/notification_bloc.js.map +1 -1
- package/lib/module/modules/notification/presentation/bloc/notification_state.js +7 -1
- package/lib/module/modules/notification/presentation/bloc/notification_state.js.map +1 -1
- package/lib/module/modules/notification/presentation/pages/notification_page_liste.js +6 -2
- package/lib/module/modules/notification/presentation/pages/notification_page_liste.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js +2 -2
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js +2 -2
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js +156 -25
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/web/profile_web_page.js.map +1 -1
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page.js.map +1 -1
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js +2 -2
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js.map +1 -1
- package/lib/module/modules/search/domain/models/fonctionnalite.js +199 -0
- package/lib/module/modules/search/domain/models/fonctionnalite.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js +119 -0
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_page.js +359 -0
- package/lib/module/modules/search/presentation/pages/search_page.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_page_voice_button.js +335 -0
- package/lib/module/modules/search/presentation/pages/search_page_voice_button.js.map +1 -0
- package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js +2 -2
- package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js.map +1 -1
- package/lib/module/modules/transactions/domain/models/transaction_send/transaction_send_command.js +6 -2
- package/lib/module/modules/transactions/domain/models/transaction_send/transaction_send_command.js.map +1 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js +13 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js.map +1 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js +7 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.js +3 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_jour_libelle.js +15 -0
- package/lib/module/modules/transactions/presentation/pages/transaction_jour_libelle.js.map +1 -0
- package/lib/module/modules/transactions/presentation/pages/transaction_list_item_widget.js +2 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_list_item_widget.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.js +5 -3
- package/lib/module/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js +216 -137
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js +8 -3
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js +3 -3
- package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js.map +1 -1
- package/lib/module/shared/widgets/amount_widget.js +4 -1
- package/lib/module/shared/widgets/amount_widget.js.map +1 -1
- package/lib/module/shared/widgets/input_amount.js.map +1 -1
- package/lib/module/shared/widgets/input_text.js +2 -0
- package/lib/module/shared/widgets/input_text.js.map +1 -1
- package/lib/module/shared/widgets/menu_actions_widget.js +10 -1
- package/lib/module/shared/widgets/menu_actions_widget.js.map +1 -1
- package/lib/module/shared/widgets/pagination_loader_widget.js +29 -0
- package/lib/module/shared/widgets/pagination_loader_widget.js.map +1 -0
- package/lib/typescript/src/core/assets.d.ts +1 -0
- package/lib/typescript/src/core/assets.d.ts.map +1 -1
- package/lib/typescript/src/core/di.d.ts.map +1 -1
- package/lib/typescript/src/core/pi_spi_custom_theme.d.ts +49 -0
- package/lib/typescript/src/core/pi_spi_custom_theme.d.ts.map +1 -0
- package/lib/typescript/src/core/pi_spi_sdk_config.d.ts +15 -2
- package/lib/typescript/src/core/pi_spi_sdk_config.d.ts.map +1 -1
- package/lib/typescript/src/core/router.d.ts +1 -0
- package/lib/typescript/src/core/router.d.ts.map +1 -1
- package/lib/typescript/src/core/routes.d.ts.map +1 -1
- package/lib/typescript/src/core/storage.d.ts +11 -3
- package/lib/typescript/src/core/storage.d.ts.map +1 -1
- package/lib/typescript/src/core/theme.d.ts +19 -0
- package/lib/typescript/src/core/theme.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations.d.ts +46 -0
- package/lib/typescript/src/l10n/app_localizations.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_en.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_fr.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_pt.d.ts.map +1 -1
- package/lib/typescript/src/modules/categorie/presentation/pages/categorie_select_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/compte/presentation/pages/solde_widget_with_qr.d.ts.map +1 -1
- package/lib/typescript/src/modules/home/presentation/pages/home_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/introduction/presentation/pages/introduction_bar.d.ts.map +1 -1
- package/lib/typescript/src/modules/introduction/presentation/pages/introduction_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_bloc.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts +5 -0
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/pages/notification_page_liste.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/web/profile_web_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts +70 -0
- package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts +21 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts +21 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts +29 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts.map +1 -0
- package/lib/typescript/src/modules/transactions/domain/models/transaction_send/transaction_send_command.d.ts +1 -1
- package/lib/typescript/src/modules/transactions/domain/models/transaction_send/transaction_send_command.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts +5 -0
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.d.ts +2 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_jour_libelle.d.ts +4 -0
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_jour_libelle.d.ts.map +1 -0
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_list_item_widget.d.ts +2 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_list_item_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts +5 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/amount_widget.d.ts +3 -1
- package/lib/typescript/src/shared/widgets/amount_widget.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/input_text.d.ts +3 -3
- package/lib/typescript/src/shared/widgets/input_text.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts +5 -1
- package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts +7 -0
- package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts.map +1 -0
- package/package.json +49 -35
- package/src/core/assets.ts +1 -0
- package/src/core/di.ts +8 -1
- package/src/core/pi_spi_custom_theme.ts +66 -0
- package/src/core/pi_spi_sdk_config.ts +18 -3
- package/src/core/router.tsx +3 -0
- package/src/core/routes.tsx +15 -0
- package/src/core/storage.ts +30 -22
- package/src/core/theme.ts +53 -0
- package/src/index.ts +2 -1
- package/src/l10n/app_localizations.ts +46 -0
- package/src/l10n/app_localizations_en.ts +23 -0
- package/src/l10n/app_localizations_fr.ts +23 -0
- package/src/l10n/app_localizations_pt.ts +23 -0
- package/src/modules/alias/presentation/pages/alias_page_create_options.tsx +3 -3
- package/src/modules/alias/presentation/pages/alias_page_otp.tsx +2 -2
- package/src/modules/categorie/presentation/pages/categorie_edit_icon_btn.tsx +2 -2
- package/src/modules/categorie/presentation/pages/categorie_select_widget.tsx +13 -9
- package/src/modules/compte/presentation/pages/solde_widget_with_qr.tsx +10 -3
- package/src/modules/home/presentation/pages/home_page.tsx +31 -2
- package/src/modules/home/presentation/pages/home_tab_compte.tsx +1 -1
- package/src/modules/home/presentation/pages/home_tab_compte_more_menu.tsx +2 -2
- package/src/modules/introduction/presentation/pages/introduction_bar.tsx +12 -2
- package/src/modules/introduction/presentation/pages/introduction_page.tsx +20 -2
- package/src/modules/notification/presentation/bloc/notification_bloc.ts +12 -3
- package/src/modules/notification/presentation/bloc/notification_state.ts +11 -1
- package/src/modules/notification/presentation/pages/notification_page_liste.tsx +4 -1
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page.tsx +5 -2
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx +2 -2
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.tsx +95 -15
- package/src/modules/profile/presentation/pages/web/profile_web_page.tsx +3 -2
- package/src/modules/qrcode/presentation/pages/qrcode_alias_page.tsx +2 -2
- package/src/modules/qrcode/presentation/pages/qrcode_alias_page_share.tsx +2 -2
- package/src/modules/search/domain/models/fonctionnalite.ts +217 -0
- package/src/modules/search/presentation/pages/search_fonctionnalites.ts +135 -0
- package/src/modules/search/presentation/pages/search_page.tsx +314 -0
- package/src/modules/search/presentation/pages/search_page_voice_button.tsx +283 -0
- package/src/modules/subscription/presentation/pages/subscription_menu_widget.tsx +2 -2
- package/src/modules/transactions/domain/models/transaction_send/transaction_send_command.ts +6 -2
- package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.ts +15 -1
- package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.ts +10 -1
- package/src/modules/transactions/presentation/pages/transaction_form/transaction_form_input_motif.tsx +3 -0
- package/src/modules/transactions/presentation/pages/transaction_jour_libelle.ts +17 -0
- package/src/modules/transactions/presentation/pages/transaction_list_item_widget.tsx +3 -1
- package/src/modules/transactions/presentation/pages/transaction_recents/transaction_recents_widget.tsx +7 -4
- package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.tsx +204 -101
- package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.tsx +6 -2
- package/src/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.tsx +3 -3
- package/src/shared/widgets/amount_widget.tsx +5 -2
- package/src/shared/widgets/input_amount.tsx +2 -2
- package/src/shared/widgets/input_text.tsx +8 -3
- package/src/shared/widgets/menu_actions_widget.tsx +8 -2
- package/src/shared/widgets/pagination_loader_widget.tsx +24 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { MaterialIcons } from '@expo/vector-icons';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { AccessibilityInfo, Animated, Easing, Platform, Pressable, StyleSheet, Vibration } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { useTheme } from '../../../../core/theme';
|
|
6
|
+
import type { ValueNotifier } from '../../../../core/value_notifier';
|
|
7
|
+
|
|
8
|
+
/** Zone tactile, plus grande que le disque pour laisser place aux ondes. */
|
|
9
|
+
const TAILLE = 40;
|
|
10
|
+
|
|
11
|
+
/** Diamètre du disque une fois actif. */
|
|
12
|
+
const DISQUE = 30;
|
|
13
|
+
|
|
14
|
+
/** Taille du disque au repos, par rapport à sa taille active. */
|
|
15
|
+
const ECHELLE_REPOS = 0.85;
|
|
16
|
+
|
|
17
|
+
/** Durée d'un cycle d'onde pendant l'écoute. */
|
|
18
|
+
const CYCLE_ONDES = 1600;
|
|
19
|
+
|
|
20
|
+
/** Nombre d'ondes, décalées d'un tiers de cycle. */
|
|
21
|
+
const NOMBRE_ONDES = 3;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Bouton micro de la recherche.
|
|
25
|
+
*
|
|
26
|
+
* Trois états visuels :
|
|
27
|
+
* - repos : micro coloré sur un disque teinté, pour se distinguer des
|
|
28
|
+
* autres icônes du champ. À l'ouverture, deux pulsations douces
|
|
29
|
+
* signalent qu'il est utilisable ;
|
|
30
|
+
* - actif ([ecoute]) : disque plein qui grossit avec un léger rebond,
|
|
31
|
+
* et trois ondes qui en partent en continu ;
|
|
32
|
+
* - appui : le bouton s'enfonce légèrement, avec une vibration (Android).
|
|
33
|
+
*
|
|
34
|
+
* Les ondes et l'invitation sont coupées si le téléphone demande de
|
|
35
|
+
* réduire les animations (accessibilité) ; seul le changement d'état
|
|
36
|
+
* du disque reste visible.
|
|
37
|
+
*/
|
|
38
|
+
export function SearchPageVoiceButton({
|
|
39
|
+
ecoute,
|
|
40
|
+
niveau,
|
|
41
|
+
onPress,
|
|
42
|
+
tooltip,
|
|
43
|
+
}: {
|
|
44
|
+
/** La voix est active (voix off ou écoute) : le bouton est « allumé ». */
|
|
45
|
+
ecoute: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Volume de la voix de 0 (silence) à 1 (voix forte) : plus il est
|
|
48
|
+
* haut, plus le disque grossit et plus les ondes s'éloignent.
|
|
49
|
+
*/
|
|
50
|
+
niveau: ValueNotifier<number>;
|
|
51
|
+
onPress: () => void;
|
|
52
|
+
/** Libellé du lecteur d'écran. */
|
|
53
|
+
tooltip?: string;
|
|
54
|
+
}) {
|
|
55
|
+
const theme = useTheme();
|
|
56
|
+
const couleur = theme.colorScheme.primary;
|
|
57
|
+
const surCouleur = theme.colorScheme.onPrimary;
|
|
58
|
+
const animationsReduites = useAnimationsReduites();
|
|
59
|
+
|
|
60
|
+
/** Passage repos → actif (0 → 1). */
|
|
61
|
+
const entree = useRef(new Animated.Value(ecoute ? 1 : 0)).current;
|
|
62
|
+
/** Enfoncement au toucher. */
|
|
63
|
+
const appui = useRef(new Animated.Value(0)).current;
|
|
64
|
+
/** Une pulsation d'invitation au repos, de 0 à 1. */
|
|
65
|
+
const invitation = useRef(new Animated.Value(0)).current;
|
|
66
|
+
/** [niveau] adouci, pour que le disque ne tremble pas à chaque mesure du micro. */
|
|
67
|
+
const niveauLisse = useRef(new Animated.Value(0)).current;
|
|
68
|
+
/** Avancement de chaque onde d'écoute, de 0 à 1. */
|
|
69
|
+
const ondes = useRef(Array.from({ length: NOMBRE_ONDES }, () => new Animated.Value(0))).current;
|
|
70
|
+
|
|
71
|
+
// Allume ou éteint le bouton : rebond à l'arrivée, retour plus rapide.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (ecoute) invitation.setValue(0);
|
|
74
|
+
const animation = ecoute
|
|
75
|
+
? Animated.spring(entree, { toValue: 1, friction: 5, tension: 120, useNativeDriver: true })
|
|
76
|
+
: Animated.timing(entree, { toValue: 0, duration: 240, easing: Easing.in(Easing.cubic), useNativeDriver: true });
|
|
77
|
+
animation.start();
|
|
78
|
+
return () => animation.stop();
|
|
79
|
+
}, [ecoute, entree, invitation]);
|
|
80
|
+
|
|
81
|
+
// Ondes en boucle pendant l'écoute, décalées d'un tiers de cycle.
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!ecoute || animationsReduites) return;
|
|
84
|
+
const boucles = ondes.map((onde, i) => {
|
|
85
|
+
onde.setValue(0);
|
|
86
|
+
return Animated.sequence([
|
|
87
|
+
Animated.delay((CYCLE_ONDES / NOMBRE_ONDES) * i),
|
|
88
|
+
Animated.loop(
|
|
89
|
+
Animated.timing(onde, { toValue: 1, duration: CYCLE_ONDES, easing: Easing.linear, useNativeDriver: true }),
|
|
90
|
+
),
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
boucles.forEach((boucle) => boucle.start());
|
|
94
|
+
return () => boucles.forEach((boucle) => boucle.stop());
|
|
95
|
+
}, [ecoute, animationsReduites, ondes]);
|
|
96
|
+
|
|
97
|
+
// Le volume suit la voix en douceur, et retombe à 0 hors écoute.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const suivre = () =>
|
|
100
|
+
Animated.timing(niveauLisse, {
|
|
101
|
+
toValue: ecoute ? Math.min(Math.max(niveau.value, 0), 1) : 0,
|
|
102
|
+
duration: 120,
|
|
103
|
+
useNativeDriver: true,
|
|
104
|
+
}).start();
|
|
105
|
+
suivre();
|
|
106
|
+
return niveau.addListener(suivre);
|
|
107
|
+
}, [ecoute, niveau, niveauLisse]);
|
|
108
|
+
|
|
109
|
+
// Invitation : deux pulsations, une seule fois par ouverture de la page,
|
|
110
|
+
// après l'entrée de la page pour que l'œil la voie.
|
|
111
|
+
const invitationLancee = useRef(false);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (invitationLancee.current || animationsReduites !== false) return;
|
|
114
|
+
invitationLancee.current = true;
|
|
115
|
+
if (ecoute) return;
|
|
116
|
+
const pulsation = () => Animated.timing(invitation, { toValue: 1, duration: 1100, useNativeDriver: true });
|
|
117
|
+
invitation.setValue(0);
|
|
118
|
+
const animation = Animated.sequence([
|
|
119
|
+
Animated.delay(500),
|
|
120
|
+
pulsation(),
|
|
121
|
+
Animated.timing(invitation, { toValue: 0, duration: 0, useNativeDriver: true }),
|
|
122
|
+
pulsation(),
|
|
123
|
+
]);
|
|
124
|
+
animation.start();
|
|
125
|
+
return () => animation.stop();
|
|
126
|
+
}, [animationsReduites, ecoute, invitation]);
|
|
127
|
+
|
|
128
|
+
const toucher = () => {
|
|
129
|
+
// Sur iOS, `Vibration` ne sait faire qu'une longue vibration : pas de retour haptique.
|
|
130
|
+
if (Platform.OS === 'android') Vibration.vibrate(10);
|
|
131
|
+
onPress();
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const presence = entree.interpolate({ inputRange: [0, 1], outputRange: [0, 1], extrapolate: 'clamp' });
|
|
135
|
+
// 0 → 1 → 0 sur une pulsation d'invitation.
|
|
136
|
+
const souffle = invitation.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, 1, 0] });
|
|
137
|
+
const echelle = Animated.multiply(
|
|
138
|
+
Animated.multiply(
|
|
139
|
+
entree.interpolate({ inputRange: [0, 1, 1.2], outputRange: [ECHELLE_REPOS, 1, 1.03], extrapolate: 'clamp' }),
|
|
140
|
+
niveauLisse.interpolate({ inputRange: [0, 1], outputRange: [1, 1.16] }),
|
|
141
|
+
),
|
|
142
|
+
souffle.interpolate({ inputRange: [0, 1], outputRange: [1, 1.1] }),
|
|
143
|
+
);
|
|
144
|
+
const enfoncement = appui.interpolate({ inputRange: [0, 1], outputRange: [1, 0.86] });
|
|
145
|
+
const rayonBase = DISQUE / 2;
|
|
146
|
+
const rayonRepos = (DISQUE / 2) * ECHELLE_REPOS;
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Pressable
|
|
150
|
+
accessibilityRole="button"
|
|
151
|
+
accessibilityLabel={tooltip}
|
|
152
|
+
accessibilityState={{ selected: ecoute }}
|
|
153
|
+
onPressIn={() => Animated.timing(appui, { toValue: 1, duration: 90, useNativeDriver: true }).start()}
|
|
154
|
+
onPressOut={() => Animated.timing(appui, { toValue: 0, duration: 160, useNativeDriver: true }).start()}
|
|
155
|
+
onPress={toucher}
|
|
156
|
+
style={styles.zone}
|
|
157
|
+
>
|
|
158
|
+
{!animationsReduites && (
|
|
159
|
+
<>
|
|
160
|
+
{/* Onde d'invitation, au repos */}
|
|
161
|
+
<Animated.View
|
|
162
|
+
pointerEvents="none"
|
|
163
|
+
style={[
|
|
164
|
+
styles.onde,
|
|
165
|
+
{
|
|
166
|
+
width: rayonRepos * 2,
|
|
167
|
+
height: rayonRepos * 2,
|
|
168
|
+
borderRadius: rayonRepos,
|
|
169
|
+
backgroundColor: couleur,
|
|
170
|
+
opacity: Animated.multiply(
|
|
171
|
+
invitation.interpolate({ inputRange: [0, 0.01, 0.5, 1], outputRange: [0, 0.4, 0.1, 0] }),
|
|
172
|
+
presence.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
|
|
173
|
+
),
|
|
174
|
+
transform: [
|
|
175
|
+
{
|
|
176
|
+
scale: easeOutQuad(invitation).interpolate({
|
|
177
|
+
inputRange: [0, 1],
|
|
178
|
+
outputRange: [1, (rayonRepos + 10) / rayonRepos],
|
|
179
|
+
}),
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
]}
|
|
184
|
+
/>
|
|
185
|
+
{/* Ondes d'écoute : plus amples et plus visibles quand la voix est forte */}
|
|
186
|
+
{ondes.map((onde, i) => (
|
|
187
|
+
<Animated.View
|
|
188
|
+
key={i}
|
|
189
|
+
pointerEvents="none"
|
|
190
|
+
style={[
|
|
191
|
+
styles.onde,
|
|
192
|
+
{
|
|
193
|
+
width: DISQUE,
|
|
194
|
+
height: DISQUE,
|
|
195
|
+
borderRadius: rayonBase,
|
|
196
|
+
backgroundColor: couleur,
|
|
197
|
+
opacity: Animated.multiply(
|
|
198
|
+
Animated.multiply(
|
|
199
|
+
onde.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0.5, 0.125, 0] }),
|
|
200
|
+
presence,
|
|
201
|
+
),
|
|
202
|
+
niveauLisse.interpolate({ inputRange: [0, 1], outputRange: [0.45, 1] }),
|
|
203
|
+
),
|
|
204
|
+
transform: [
|
|
205
|
+
{
|
|
206
|
+
scale: Animated.add(
|
|
207
|
+
1,
|
|
208
|
+
Animated.multiply(
|
|
209
|
+
easeOutQuad(onde),
|
|
210
|
+
niveauLisse.interpolate({ inputRange: [0, 1], outputRange: [6 / rayonBase, 16 / rayonBase] }),
|
|
211
|
+
),
|
|
212
|
+
),
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
]}
|
|
217
|
+
/>
|
|
218
|
+
))}
|
|
219
|
+
</>
|
|
220
|
+
)}
|
|
221
|
+
<Animated.View style={[styles.centre, { transform: [{ scale: enfoncement }] }]}>
|
|
222
|
+
{/* Disque : teinté au repos, plein une fois actif */}
|
|
223
|
+
<Animated.View style={[styles.disque, { transform: [{ scale: echelle }] }]}>
|
|
224
|
+
<Animated.View
|
|
225
|
+
style={[
|
|
226
|
+
StyleSheet.absoluteFill,
|
|
227
|
+
styles.rond,
|
|
228
|
+
{ backgroundColor: couleur, opacity: souffle.interpolate({ inputRange: [0, 1], outputRange: [0.12, 0.2] }) },
|
|
229
|
+
]}
|
|
230
|
+
/>
|
|
231
|
+
<Animated.View style={[StyleSheet.absoluteFill, styles.rond, { backgroundColor: couleur, opacity: presence }]} />
|
|
232
|
+
</Animated.View>
|
|
233
|
+
{/* Micro : contour au repos, plein pendant l'écoute */}
|
|
234
|
+
<Animated.View style={[styles.icone, { opacity: presence.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }) }]}>
|
|
235
|
+
<MaterialIcons name="mic-none" size={20} color={couleur} />
|
|
236
|
+
</Animated.View>
|
|
237
|
+
<Animated.View style={[styles.icone, { opacity: presence, transform: [{ scale: presence }] }]}>
|
|
238
|
+
<MaterialIcons name="mic" size={20} color={surCouleur} />
|
|
239
|
+
</Animated.View>
|
|
240
|
+
</Animated.View>
|
|
241
|
+
</Pressable>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Courbe `Easing.out(Easing.quad)` (1 - (1 - t)²) appliquée à une valeur
|
|
247
|
+
* de 0 à 1, décrite point par point : `interpolate` n'accepte pas d'option
|
|
248
|
+
* `easing`, et le pilote natif rejette l'animation (erreur « Interpolation
|
|
249
|
+
* property 'easing' is not supported by native animated module » à chaque
|
|
250
|
+
* image).
|
|
251
|
+
*/
|
|
252
|
+
function easeOutQuad(valeur: Animated.Value): Animated.AnimatedInterpolation<number> {
|
|
253
|
+
return valeur.interpolate({
|
|
254
|
+
inputRange: [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1],
|
|
255
|
+
outputRange: [0, 0.234375, 0.4375, 0.609375, 0.75, 0.859375, 0.9375, 0.984375, 1],
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Réglage « réduire les animations » du téléphone ; `undefined` tant qu'il n'est pas lu. */
|
|
260
|
+
function useAnimationsReduites(): boolean | undefined {
|
|
261
|
+
const [reduites, setReduites] = useState<boolean | undefined>(undefined);
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
let actif = true;
|
|
264
|
+
void AccessibilityInfo.isReduceMotionEnabled().then((valeur) => {
|
|
265
|
+
if (actif) setReduites(valeur);
|
|
266
|
+
});
|
|
267
|
+
const abonnement = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduites);
|
|
268
|
+
return () => {
|
|
269
|
+
actif = false;
|
|
270
|
+
abonnement.remove();
|
|
271
|
+
};
|
|
272
|
+
}, []);
|
|
273
|
+
return reduites;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const styles = StyleSheet.create({
|
|
277
|
+
zone: { width: TAILLE, height: TAILLE, alignItems: 'center', justifyContent: 'center' },
|
|
278
|
+
onde: { position: 'absolute' },
|
|
279
|
+
centre: { width: DISQUE, height: DISQUE, alignItems: 'center', justifyContent: 'center' },
|
|
280
|
+
disque: { position: 'absolute', width: DISQUE, height: DISQUE },
|
|
281
|
+
rond: { borderRadius: DISQUE / 2 },
|
|
282
|
+
icone: { position: 'absolute' },
|
|
283
|
+
});
|
|
@@ -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,
|
|
9
|
+
import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
|
|
10
10
|
|
|
11
11
|
/** Menu de création : programmer un paiement ou souscrire à un abonnement (feuille du bas) */
|
|
12
12
|
export function SubscriptionMenuWidget() {
|
|
@@ -27,7 +27,7 @@ export function SubscriptionMenuWidget() {
|
|
|
27
27
|
<Text style={theme.textTheme.headlineMedium}>{traductions.subscriptionEmptyBtnCreate}</Text>
|
|
28
28
|
<View style={styles.gap20} />
|
|
29
29
|
<Card>
|
|
30
|
-
<
|
|
30
|
+
<MenuActionsWidget
|
|
31
31
|
items={[
|
|
32
32
|
// Programmer
|
|
33
33
|
new MenuActionItem(
|
|
@@ -4,7 +4,7 @@ import { TransactionActions } from './transaction_actions';
|
|
|
4
4
|
import { TransactionSendCommandAlias } from './transaction_send_command_alias';
|
|
5
5
|
import { TransactionSendCommandAmount } from './transaction_send_command_amount';
|
|
6
6
|
import type { TransactionSendCommandContact } from './transaction_send_command_contact';
|
|
7
|
-
import
|
|
7
|
+
import { TransactionSendCommandMotif } from './transaction_send_command_motif';
|
|
8
8
|
import { TransactionSendCommandOthr } from './transaction_send_command_othr';
|
|
9
9
|
import type { TransactionSendCommandSchedule } from './transaction_send_command_schedule';
|
|
10
10
|
import { TransactionSendMethod } from './transaction_send_method';
|
|
@@ -109,7 +109,7 @@ export class TransactionSendCommand {
|
|
|
109
109
|
longitude: this.longitude ?? null,
|
|
110
110
|
};
|
|
111
111
|
if (this.txId != null) json.txId = this.txId;
|
|
112
|
-
if (this.motif != null) json.motif = this.motif.value
|
|
112
|
+
if (this.motif?.value != null && this.motif.value.length > 0) json.motif = this.motif.value;
|
|
113
113
|
if (this.schedule != null) {
|
|
114
114
|
json.dateDebut = this.schedule.dateDebut;
|
|
115
115
|
if (this.schedule.frequence != null) {
|
|
@@ -163,6 +163,10 @@ export class TransactionSendCommand {
|
|
|
163
163
|
pspPays: transaction.clientPays,
|
|
164
164
|
pspNom: transaction.clientPSPNom,
|
|
165
165
|
amount: new TransactionSendCommandAmount({ action, value: transaction.montant }),
|
|
166
|
+
motif:
|
|
167
|
+
transaction.motif != null && !transaction.motif.startsWith('@')
|
|
168
|
+
? new TransactionSendCommandMotif({ value: transaction.motif })
|
|
169
|
+
: undefined,
|
|
166
170
|
});
|
|
167
171
|
}
|
|
168
172
|
}
|
package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.ts
CHANGED
|
@@ -52,8 +52,13 @@ export class TransactionSearchBloc extends Bloc<TransactionSearchEvent, Transact
|
|
|
52
52
|
/** Quand on demande la liste des transactions */
|
|
53
53
|
private async onList(event: TransactionSearchListEvent, emit: Emitter<TransactionSearchState>): Promise<void> {
|
|
54
54
|
emit(new TransactionSearchLoadingState(this.state.transactions, event.command));
|
|
55
|
+
|
|
56
|
+
// Repartir de la première page
|
|
57
|
+
const command = event.command;
|
|
58
|
+
command.index = null;
|
|
59
|
+
command.total = 0;
|
|
60
|
+
|
|
55
61
|
try {
|
|
56
|
-
const command = event.command;
|
|
57
62
|
// Rechercher d'abord en local et afficher
|
|
58
63
|
const donneesLocales = await this.transactionsInputPort.list(this.params(command));
|
|
59
64
|
if (command.filters.categories.length > 0) {
|
|
@@ -76,16 +81,25 @@ export class TransactionSearchBloc extends Bloc<TransactionSearchEvent, Transact
|
|
|
76
81
|
|
|
77
82
|
/** Pagination */
|
|
78
83
|
private async onPaginate(event: TransactionSearchPaginateEvent, emit: Emitter<TransactionSearchState>): Promise<void> {
|
|
84
|
+
// Une page est déjà en cours de chargement
|
|
85
|
+
const current = this.state;
|
|
86
|
+
if (!(current instanceof TransactionSearchListState) || current.isPaginating) return;
|
|
87
|
+
|
|
79
88
|
const command = event.command;
|
|
89
|
+
// Affiche le loader en bas de la liste
|
|
90
|
+
emit(new TransactionSearchListState(current.transactions, command, true));
|
|
80
91
|
try {
|
|
81
92
|
const liste = await this.transactionsInputPort.search(this.params(command, command.index));
|
|
82
93
|
command.total = liste.meta.total;
|
|
83
94
|
command.index = liste.meta.next as number | string | null | undefined;
|
|
84
95
|
if (liste.isNotEmpty) {
|
|
85
96
|
emit(new TransactionSearchListState(new TransactionListe([...this.state.transactions.data, ...liste.data], liste.meta), command));
|
|
97
|
+
return;
|
|
86
98
|
}
|
|
87
99
|
} catch (e) {
|
|
88
100
|
this.logger.e('Recuperer liste transaction pagination error =>', { error: e });
|
|
89
101
|
}
|
|
102
|
+
// Page vide ou erreur : on retire le loader
|
|
103
|
+
emit(new TransactionSearchListState(this.state.transactions, command));
|
|
90
104
|
}
|
|
91
105
|
}
|
package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.ts
CHANGED
|
@@ -12,4 +12,13 @@ export class TransactionSearchInitialState extends TransactionSearchState {}
|
|
|
12
12
|
|
|
13
13
|
export class TransactionSearchLoadingState extends TransactionSearchState {}
|
|
14
14
|
|
|
15
|
-
export class TransactionSearchListState extends TransactionSearchState {
|
|
15
|
+
export class TransactionSearchListState extends TransactionSearchState {
|
|
16
|
+
constructor(
|
|
17
|
+
transactions: TransactionListe,
|
|
18
|
+
command: TransactionSearchCommand,
|
|
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(transactions, command);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -12,14 +12,17 @@ import { TransactionSendFormChangedEvent } from '../../bloc/transaction_send/tra
|
|
|
12
12
|
export function TransactionFormInputMotif({
|
|
13
13
|
command,
|
|
14
14
|
transactionFormBloc,
|
|
15
|
+
readOnly = false,
|
|
15
16
|
}: {
|
|
16
17
|
command: TransactionSendCommand;
|
|
17
18
|
transactionFormBloc: TransactionSendBloc;
|
|
19
|
+
readOnly?: boolean;
|
|
18
20
|
}) {
|
|
19
21
|
const traductions = useAppLocalizations();
|
|
20
22
|
return (
|
|
21
23
|
<CustomTextMultipleInput
|
|
22
24
|
labelText={traductions.transactionFormMotifHint}
|
|
25
|
+
readOnly={readOnly}
|
|
23
26
|
defaultValue={command.motif?.value}
|
|
24
27
|
messageError={command.motif?.error === TransactionSendCommandMotifError.invalid ? traductions.transactionFormMotifInvalid : ''}
|
|
25
28
|
onChange={(value) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AppLocalizations } from '../../../../l10n/app_localizations';
|
|
2
|
+
import { DateFormat } from '../../../../shared/utils/date_format';
|
|
3
|
+
|
|
4
|
+
/** « Aujourd'hui », « Hier » ou la date du jour [jour] (aaaa-mm-jj) */
|
|
5
|
+
export function transactionJourLibelle(traductions: AppLocalizations, jour: string): string {
|
|
6
|
+
const date = new Date(`${jour}T00:00:00`);
|
|
7
|
+
const maintenant = new Date();
|
|
8
|
+
// En UTC : pas de journée de 23 h aux changements d'heure
|
|
9
|
+
const ecart = Math.round(
|
|
10
|
+
(Date.UTC(maintenant.getFullYear(), maintenant.getMonth(), maintenant.getDate()) -
|
|
11
|
+
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())) /
|
|
12
|
+
86400000,
|
|
13
|
+
);
|
|
14
|
+
if (ecart === 0) return traductions.transactionsAujourdhui;
|
|
15
|
+
if (ecart === 1) return traductions.transactionsHier;
|
|
16
|
+
return new DateFormat('d MMMM y', traductions.localeName).format(date);
|
|
17
|
+
}
|
|
@@ -16,12 +16,14 @@ import { TransactionSens, type Transaction } from '../../domain/models/transacti
|
|
|
16
16
|
*/
|
|
17
17
|
export function TransactionListItemWidget({
|
|
18
18
|
transaction,
|
|
19
|
+
datePartern = 'd MMM, HH:mm',
|
|
19
20
|
detailsBackRoute,
|
|
20
21
|
noLink,
|
|
21
22
|
onSelect,
|
|
22
23
|
isSelected,
|
|
23
24
|
}: {
|
|
24
25
|
transaction: Transaction;
|
|
26
|
+
datePartern?: string;
|
|
25
27
|
detailsBackRoute?: string;
|
|
26
28
|
noLink?: boolean;
|
|
27
29
|
onSelect?: (value: boolean) => void;
|
|
@@ -65,7 +67,7 @@ export function TransactionListItemWidget({
|
|
|
65
67
|
{/* Date d'irrévocabilité */}
|
|
66
68
|
{transaction.dateOperation != null && (
|
|
67
69
|
<Text style={[theme.textTheme.displaySmall, { color: Themer.neural04Color }]}>
|
|
68
|
-
{new DateFormat(
|
|
70
|
+
{new DateFormat(datePartern, locale).format(transaction.dateOperation)}
|
|
69
71
|
</Text>
|
|
70
72
|
)}
|
|
71
73
|
</View>
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from '../../bloc/transaction_recents/transaction_recents_state';
|
|
20
20
|
import { TransactionListItemWidget } from '../transaction_list_item_widget';
|
|
21
21
|
import { TransactionListLoadingWidget } from '../transaction_list_loading_widget';
|
|
22
|
+
import { groupTransactions } from '../transaction_search/transaction_search_page_liste';
|
|
22
23
|
import { TransactionRecentsNombreWidget } from './transaction_recents_nombre_widget';
|
|
23
24
|
|
|
24
25
|
/** Dernières transactions du compte actif, sur l'accueil. */
|
|
@@ -63,10 +64,12 @@ export function TransactionRecentsWidget() {
|
|
|
63
64
|
<MaterialIcons name="more-horiz" size={24} color={theme.colorScheme.onSurface} />
|
|
64
65
|
</Pressable>
|
|
65
66
|
</View>
|
|
66
|
-
{/* Liste */}
|
|
67
|
-
{transactions.
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
{/* Liste regroupée par jour */}
|
|
68
|
+
{groupTransactions(transactions).flatMap((groupe) =>
|
|
69
|
+
groupe.transactions.map((transaction) => (
|
|
70
|
+
<TransactionListItemWidget key={transaction.endToEndId} transaction={transaction} datePartern="d MMM y, HH:mm" />
|
|
71
|
+
)),
|
|
72
|
+
)}
|
|
70
73
|
<View style={styles.gap12} />
|
|
71
74
|
{/* Tout afficher */}
|
|
72
75
|
<TextButton onPress={() => void AppRouter.push(router, AppRouter.transactionSearch)}>
|