@pi-spi/app 1.5.0 → 1.6.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 (117) hide show
  1. package/README.md +3 -0
  2. package/lib/module/core/api.js +9 -0
  3. package/lib/module/core/api.js.map +1 -1
  4. package/lib/module/core/pi_spi_sdk_config.js +10 -0
  5. package/lib/module/core/pi_spi_sdk_config.js.map +1 -1
  6. package/lib/module/l10n/app_localizations.js.map +1 -1
  7. package/lib/module/l10n/app_localizations_en.js +12 -1
  8. package/lib/module/l10n/app_localizations_en.js.map +1 -1
  9. package/lib/module/l10n/app_localizations_fr.js +12 -1
  10. package/lib/module/l10n/app_localizations_fr.js.map +1 -1
  11. package/lib/module/l10n/app_localizations_pt.js +12 -1
  12. package/lib/module/l10n/app_localizations_pt.js.map +1 -1
  13. package/lib/module/modules/alias/presentation/pages/alias_delete_btn_widget.js +15 -19
  14. package/lib/module/modules/alias/presentation/pages/alias_delete_btn_widget.js.map +1 -1
  15. package/lib/module/modules/alias/presentation/pages/alias_page_claim.js +4 -11
  16. package/lib/module/modules/alias/presentation/pages/alias_page_claim.js.map +1 -1
  17. package/lib/module/modules/compte/presentation/pages/compte_details_widget.js +6 -7
  18. package/lib/module/modules/compte/presentation/pages/compte_details_widget.js.map +1 -1
  19. package/lib/module/modules/profile/presentation/pages/compte/profile_compte_page.js +25 -11
  20. package/lib/module/modules/profile/presentation/pages/compte/profile_compte_page.js.map +1 -1
  21. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js +12 -18
  22. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js.map +1 -1
  23. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js +109 -103
  24. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js.map +1 -1
  25. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications_son.js +55 -72
  26. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications_son.js.map +1 -1
  27. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.js +31 -45
  28. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.js.map +1 -1
  29. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js +103 -67
  30. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js.map +1 -1
  31. package/lib/module/modules/profile/presentation/pages/profile_app_bar.js +26 -0
  32. package/lib/module/modules/profile/presentation/pages/profile_app_bar.js.map +1 -0
  33. package/lib/module/modules/profile/presentation/pages/profile_menu.js +143 -34
  34. package/lib/module/modules/profile/presentation/pages/profile_menu.js.map +1 -1
  35. package/lib/module/modules/profile/presentation/pages/profile_page.js +95 -63
  36. package/lib/module/modules/profile/presentation/pages/profile_page.js.map +1 -1
  37. package/lib/module/modules/profile/presentation/pages/profile_page_avatar.js +97 -45
  38. package/lib/module/modules/profile/presentation/pages/profile_page_avatar.js.map +1 -1
  39. package/lib/module/modules/profile/presentation/pages/profile_page_contacts.js +9 -8
  40. package/lib/module/modules/profile/presentation/pages/profile_page_contacts.js.map +1 -1
  41. package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js +26 -2
  42. package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js.map +1 -1
  43. package/lib/module/modules/search/presentation/pages/search_page.js +0 -1
  44. package/lib/module/modules/search/presentation/pages/search_page.js.map +1 -1
  45. package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.js +4 -11
  46. package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.js.map +1 -1
  47. package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.js +13 -10
  48. package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.js.map +1 -1
  49. package/lib/module/shared/utils/color.js +15 -0
  50. package/lib/module/shared/utils/color.js.map +1 -0
  51. package/lib/module/shared/widgets/error_card_widget.js +70 -0
  52. package/lib/module/shared/widgets/error_card_widget.js.map +1 -0
  53. package/lib/module/shared/widgets/selection.js +136 -0
  54. package/lib/module/shared/widgets/selection.js.map +1 -0
  55. package/lib/typescript/src/core/api.d.ts.map +1 -1
  56. package/lib/typescript/src/core/pi_spi_sdk_config.d.ts +10 -0
  57. package/lib/typescript/src/core/pi_spi_sdk_config.d.ts.map +1 -1
  58. package/lib/typescript/src/l10n/app_localizations.d.ts +22 -0
  59. package/lib/typescript/src/l10n/app_localizations.d.ts.map +1 -1
  60. package/lib/typescript/src/l10n/app_localizations_en.d.ts.map +1 -1
  61. package/lib/typescript/src/l10n/app_localizations_fr.d.ts.map +1 -1
  62. package/lib/typescript/src/l10n/app_localizations_pt.d.ts.map +1 -1
  63. package/lib/typescript/src/modules/alias/presentation/pages/alias_delete_btn_widget.d.ts.map +1 -1
  64. package/lib/typescript/src/modules/alias/presentation/pages/alias_page_claim.d.ts.map +1 -1
  65. package/lib/typescript/src/modules/compte/presentation/pages/compte_details_widget.d.ts.map +1 -1
  66. package/lib/typescript/src/modules/profile/presentation/pages/compte/profile_compte_page.d.ts.map +1 -1
  67. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page.d.ts.map +1 -1
  68. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.d.ts.map +1 -1
  69. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications_son.d.ts.map +1 -1
  70. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.d.ts.map +1 -1
  71. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.d.ts.map +1 -1
  72. package/lib/typescript/src/modules/profile/presentation/pages/profile_app_bar.d.ts +5 -0
  73. package/lib/typescript/src/modules/profile/presentation/pages/profile_app_bar.d.ts.map +1 -0
  74. package/lib/typescript/src/modules/profile/presentation/pages/profile_menu.d.ts +31 -4
  75. package/lib/typescript/src/modules/profile/presentation/pages/profile_menu.d.ts.map +1 -1
  76. package/lib/typescript/src/modules/profile/presentation/pages/profile_page.d.ts +4 -1
  77. package/lib/typescript/src/modules/profile/presentation/pages/profile_page.d.ts.map +1 -1
  78. package/lib/typescript/src/modules/profile/presentation/pages/profile_page_avatar.d.ts +1 -1
  79. package/lib/typescript/src/modules/profile/presentation/pages/profile_page_avatar.d.ts.map +1 -1
  80. package/lib/typescript/src/modules/profile/presentation/pages/profile_page_contacts.d.ts.map +1 -1
  81. package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts.map +1 -1
  82. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.d.ts.map +1 -1
  83. package/lib/typescript/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.d.ts.map +1 -1
  84. package/lib/typescript/src/shared/utils/color.d.ts +7 -0
  85. package/lib/typescript/src/shared/utils/color.d.ts.map +1 -0
  86. package/lib/typescript/src/shared/widgets/error_card_widget.d.ts +11 -0
  87. package/lib/typescript/src/shared/widgets/error_card_widget.d.ts.map +1 -0
  88. package/lib/typescript/src/shared/widgets/selection.d.ts +21 -0
  89. package/lib/typescript/src/shared/widgets/selection.d.ts.map +1 -0
  90. package/package.json +1 -1
  91. package/src/core/api.ts +4 -0
  92. package/src/core/pi_spi_sdk_config.ts +14 -0
  93. package/src/l10n/app_localizations.ts +22 -0
  94. package/src/l10n/app_localizations_en.ts +11 -0
  95. package/src/l10n/app_localizations_fr.ts +11 -0
  96. package/src/l10n/app_localizations_pt.ts +11 -0
  97. package/src/modules/alias/presentation/pages/alias_delete_btn_widget.tsx +13 -12
  98. package/src/modules/alias/presentation/pages/alias_page_claim.tsx +5 -4
  99. package/src/modules/compte/presentation/pages/compte_details_widget.tsx +3 -4
  100. package/src/modules/profile/presentation/pages/compte/profile_compte_page.tsx +15 -7
  101. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page.tsx +10 -12
  102. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx +89 -68
  103. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications_son.tsx +84 -54
  104. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.tsx +28 -24
  105. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.tsx +77 -44
  106. package/src/modules/profile/presentation/pages/profile_app_bar.tsx +21 -0
  107. package/src/modules/profile/presentation/pages/profile_menu.tsx +126 -33
  108. package/src/modules/profile/presentation/pages/profile_page.tsx +90 -66
  109. package/src/modules/profile/presentation/pages/profile_page_avatar.tsx +69 -28
  110. package/src/modules/profile/presentation/pages/profile_page_contacts.tsx +5 -4
  111. package/src/modules/search/presentation/pages/search_fonctionnalites.ts +40 -7
  112. package/src/modules/search/presentation/pages/search_page.tsx +1 -1
  113. package/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.tsx +2 -4
  114. package/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.tsx +13 -6
  115. package/src/shared/utils/color.ts +14 -0
  116. package/src/shared/widgets/error_card_widget.tsx +58 -0
  117. package/src/shared/widgets/selection.tsx +98 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Équivalent de `Color.withValues(alpha:)` : la couleur [color] avec
3
+ * l'opacité [alpha] (0 à 1). Accepte `#RGB`, `#RRGGBB` et `#RRGGBBAA` ;
4
+ * toute autre forme est rendue telle quelle.
5
+ */
6
+ export declare function withAlpha(color: string, alpha: number): string;
7
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../../../src/shared/utils/color.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ9D"}
@@ -0,0 +1,11 @@
1
+ import { MaterialIcons } from '@expo/vector-icons';
2
+ import type { ComponentProps } from 'react';
3
+ /**
4
+ * Carte d'avertissement : fond teinté, bordure et icône dans la couleur
5
+ * d'erreur du thème, suivies du `message`.
6
+ */
7
+ export declare function ErrorCardWidget({ message, icon, }: {
8
+ message: string;
9
+ icon?: ComponentProps<typeof MaterialIcons>['name'];
10
+ }): import("react").JSX.Element;
11
+ //# sourceMappingURL=error_card_widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error_card_widget.d.ts","sourceRoot":"","sources":["../../../../../src/shared/widgets/error_card_widget.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAY5C;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,IAAsB,GACvB,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;CACrD,+BAqBA"}
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Feuille de sélection : poignée, [titre], [entete] facultatif, puis le
4
+ * [contenu] (qui défile s'il est trop long : utiliser un ScrollView).
5
+ * À ouvrir avec showModalBottomSheet.
6
+ */
7
+ export declare function SbSelectionSheet({ titre, titreSize, entete, contenu, }: {
8
+ titre?: string;
9
+ titreSize?: number;
10
+ entete?: ReactNode;
11
+ contenu: ReactNode;
12
+ }): import("react").JSX.Element;
13
+ /** Ligne d'une feuille de sélection : [pastille], [texte], [detail] facultatif et coche si elle est [choisie]. */
14
+ export declare function SbSelectionLigne({ pastille, texte, detail, choisie, onPress, }: {
15
+ pastille: ReactNode;
16
+ texte: string;
17
+ detail?: string;
18
+ choisie: boolean;
19
+ onPress: () => void;
20
+ }): import("react").JSX.Element;
21
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../../../src/shared/widgets/selection.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOvC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,SAAS,EACT,MAAM,EACN,OAAO,GACR,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,SAAS,CAAC;CACpB,+BAqBA;AAED,kHAAkH;AAClH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BA2BA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-spi/app",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "SDK React Native permettant d'intégrer PI-SPI (transactions, demande d'annulation, retour de fond, demande de paiement, QR Code, gestion des alias, gestion des abonnements, notifications, profil, compte) dans une application mobile existante.",
5
5
  "homepage": "https://github.com/pisfn-sandbox/mobile-app-react-native-sdk.git",
6
6
  "repository": {
package/src/core/api.ts CHANGED
@@ -86,8 +86,12 @@ export class Api {
86
86
 
87
87
  /** Options communes à toutes les instances axios du SDK. */
88
88
  static baseOptions(): AxiosRequestConfig {
89
+ const { apiKey, apiKeyHeader } = Api.config.api;
89
90
  return {
90
91
  baseURL: Api.url,
92
+ // Clé d'API éventuelle : dans les options de base pour être aussi
93
+ // envoyée par les requêtes rejouées après un refresh du token.
94
+ headers: apiKey != null ? { [apiKeyHeader]: apiKey } : undefined,
91
95
  timeout: 60_000, // 60 seconds
92
96
  // Les listes sont envoyées comme `sens=debit&sens=credit` (comme Dio).
93
97
  paramsSerializer: { indexes: null },
@@ -156,18 +156,32 @@ export class PiSpiApiConfig {
156
156
  */
157
157
  readonly demoMode: boolean;
158
158
 
159
+ /**
160
+ * Clé d'API optionnelle, envoyée dans l'en-tête {@link apiKeyHeader} de
161
+ * chaque requête (en plus du token d'accès), quand la passerelle du
162
+ * participant en exige une. `undefined` : aucun en-tête n'est ajouté.
163
+ */
164
+ readonly apiKey?: string;
165
+
166
+ /** Nom de l'en-tête HTTP qui porte {@link apiKey}. Par défaut `X-API-Key`. */
167
+ readonly apiKeyHeader: string;
168
+
159
169
  constructor(params: {
160
170
  apiBaseUrl: string;
161
171
  accessToken: string;
162
172
  onAccessTokenExpired: () => Promise<string | null>;
163
173
  onSessionExpired: () => Promise<void>;
164
174
  demoMode?: boolean;
175
+ apiKey?: string;
176
+ apiKeyHeader?: string;
165
177
  }) {
166
178
  this.apiBaseUrl = params.apiBaseUrl;
167
179
  this.accessToken = params.accessToken;
168
180
  this.onAccessTokenExpired = params.onAccessTokenExpired;
169
181
  this.onSessionExpired = params.onSessionExpired;
170
182
  this.demoMode = params.demoMode ?? false;
183
+ this.apiKey = params.apiKey;
184
+ this.apiKeyHeader = params.apiKeyHeader ?? 'X-API-Key';
171
185
  }
172
186
  }
173
187
 
@@ -1389,6 +1389,28 @@ export interface AppLocalizations {
1389
1389
  readonly searchVoicePrompt: string;
1390
1390
  /** Voice search is not available. Check microphone access in settings. */
1391
1391
  readonly searchVoiceUnavailable: string;
1392
+ /** Profile */
1393
+ readonly profilePageTitle: string;
1394
+ /** My account */
1395
+ readonly profilePageSectionCompte: string;
1396
+ /** Support */
1397
+ readonly profilePageSectionAssistance: string;
1398
+ /** Preferences */
1399
+ readonly appSettingPageSectionPreferences: string;
1400
+ /** Danger zone */
1401
+ readonly comptePageSectionZoneSensible: string;
1402
+ /** Notifications */
1403
+ readonly searchNotificationsTitle: string;
1404
+ /** App theme */
1405
+ readonly searchThemeTitle: string;
1406
+ /** Notification settings */
1407
+ readonly searchNotificationsParametreTitle: string;
1408
+ /** View the notifications received */
1409
+ readonly searchNotificationsSubTitle: string;
1410
+ /** Light, dark or system, and colour */
1411
+ readonly searchThemeSubTitle: string;
1412
+ /** Notification style, sound and vibration */
1413
+ readonly searchNotificationsParametreSubTitle: string;
1392
1414
  }
1393
1415
 
1394
1416
  export const supportedLocales = ['fr', 'en', 'pt'] as const;
@@ -706,4 +706,15 @@ Scanning will be automatic`,
706
706
  searchVoiceListening: `Listening…`,
707
707
  searchVoicePrompt: `Tell me what you would like to do`,
708
708
  searchVoiceUnavailable: `Voice search is not available. Check microphone access in settings.`,
709
+ profilePageTitle: `Profile`,
710
+ profilePageSectionCompte: `My account`,
711
+ profilePageSectionAssistance: `Support`,
712
+ appSettingPageSectionPreferences: `Preferences`,
713
+ comptePageSectionZoneSensible: `Danger zone`,
714
+ searchNotificationsTitle: `Notifications`,
715
+ searchThemeTitle: `App theme`,
716
+ searchNotificationsParametreTitle: `Notification settings`,
717
+ searchNotificationsSubTitle: `View the notifications received`,
718
+ searchThemeSubTitle: `Light, dark or system, and colour`,
719
+ searchNotificationsParametreSubTitle: `Notification style, sound and vibration`,
709
720
  };
@@ -705,4 +705,15 @@ Le scan se fait automatiquement`,
705
705
  searchVoiceListening: `Je vous écoute…`,
706
706
  searchVoicePrompt: `Dites-moi ce que vous souhaitez faire`,
707
707
  searchVoiceUnavailable: `La recherche vocale n’est pas disponible. Vérifiez l’accès au micro dans les réglages.`,
708
+ profilePageTitle: `Profil`,
709
+ profilePageSectionCompte: `Mon compte`,
710
+ profilePageSectionAssistance: `Assistance`,
711
+ appSettingPageSectionPreferences: `Préférences`,
712
+ comptePageSectionZoneSensible: `Zone sensible`,
713
+ searchNotificationsTitle: `Notifications`,
714
+ searchThemeTitle: `Thème de l'application`,
715
+ searchNotificationsParametreTitle: `Réglages des notifications`,
716
+ searchNotificationsSubTitle: `Consulter les notifications reçues`,
717
+ searchThemeSubTitle: `Clair, sombre ou celui du système, et couleur`,
718
+ searchNotificationsParametreSubTitle: `Style, son et vibration des notifications`,
708
719
  };
@@ -706,4 +706,15 @@ O scan é feito automaticamente`,
706
706
  searchVoiceListening: `Estou a ouvir…`,
707
707
  searchVoicePrompt: `Diga-me o que deseja fazer`,
708
708
  searchVoiceUnavailable: `A pesquisa por voz não está disponível. Verifique o acesso ao microfone nas definições.`,
709
+ profilePageTitle: `Perfil`,
710
+ profilePageSectionCompte: `A minha conta`,
711
+ profilePageSectionAssistance: `Assistência`,
712
+ appSettingPageSectionPreferences: `Preferências`,
713
+ comptePageSectionZoneSensible: `Zona sensível`,
714
+ searchNotificationsTitle: `Notificações`,
715
+ searchThemeTitle: `Tema da aplicação`,
716
+ searchNotificationsParametreTitle: `Definições das notificações`,
717
+ searchNotificationsSubTitle: `Consultar as notificações recebidas`,
718
+ searchThemeSubTitle: `Claro, escuro ou o do sistema, e cor`,
719
+ searchNotificationsParametreSubTitle: `Estilo, som e vibração das notificações`,
709
720
  };
@@ -1,17 +1,17 @@
1
1
  import { useRef } from 'react';
2
- import { StyleSheet, View } from 'react-native';
2
+ import { StyleSheet } from 'react-native';
3
3
 
4
4
  import { useBloc, useBlocListener } from '../../../../core/bloc_react';
5
5
  import { Di } from '../../../../core/di';
6
6
  import { AppRouter, useAppRouter } from '../../../../core/router';
7
7
  import { useTheme } from '../../../../core/theme';
8
8
  import { useAppLocalizations } from '../../../../l10n/localizations';
9
- import { ElevatedButton } from '../../../../shared/material/buttons';
9
+ import { Card } from '../../../../shared/material/card';
10
10
  import { showDialog, showModalBottomSheet } from '../../../../shared/material/overlays';
11
11
  import { CustomAlertDialog } from '../../../../shared/widgets/custom_alert_dialog';
12
12
  import { CustomLoadingDialog } from '../../../../shared/widgets/loading_page';
13
- import { MyPageContainer } from '../../../../shared/widgets/my_page_container';
14
13
  import { NotificationDialog, NotificationType } from '../../../../shared/widgets/notification_dialog';
14
+ import { ProfileMenuTile } from '../../../profile/presentation/pages/profile_menu';
15
15
  import { AliasType } from '../../domain/models/alias_type';
16
16
  import { AliasBloc } from '../bloc/alias_bloc';
17
17
  import { AliasDeleteEvent } from '../bloc/alias_event';
@@ -79,18 +79,19 @@ export function AliasDeleteBtnWidget() {
79
79
  });
80
80
  };
81
81
 
82
+ // Ligne de menu du profil, en rouge
82
83
  return (
83
- <View style={styles.bottom}>
84
- <MyPageContainer style={styles.noFlex}>
85
- <ElevatedButton onPress={onPress} style={{ backgroundColor: theme.secondaryHeaderColor }}>
86
- {traductions.compteDetailsPageBtnSupprimer}
87
- </ElevatedButton>
88
- </MyPageContainer>
89
- </View>
84
+ <Card style={styles.card}>
85
+ <ProfileMenuTile
86
+ icone="delete-outline"
87
+ titre={traductions.compteDetailsPageBtnSupprimer}
88
+ couleur={theme.colorScheme.error}
89
+ onPress={onPress}
90
+ />
91
+ </Card>
90
92
  );
91
93
  }
92
94
 
93
95
  const styles = StyleSheet.create({
94
- bottom: { paddingBottom: 20 },
95
- noFlex: { flex: 0 },
96
+ card: { margin: 0, overflow: 'hidden' },
96
97
  });
@@ -14,6 +14,7 @@ import { Card } from '../../../../shared/material/card';
14
14
  import { showDialog, showModalBottomSheet } from '../../../../shared/material/overlays';
15
15
  import { DateFormat } from '../../../../shared/utils/date_format';
16
16
  import { CustomAlertDialog } from '../../../../shared/widgets/custom_alert_dialog';
17
+ import { ErrorCardWidget } from '../../../../shared/widgets/error_card_widget';
17
18
  import { CustomLoadingDialog, LoadingPage } from '../../../../shared/widgets/loading_page';
18
19
  import type { Alias } from '../../domain/models/alias';
19
20
  import { AliasError } from '../../domain/models/alias_error';
@@ -238,12 +239,13 @@ function ClaimDetails({
238
239
 
239
240
  {/* Avertissement délai de traitement */}
240
241
  {initiee && (
241
- <Text style={[theme.textTheme.bodyLarge, styles.warning, { color: Themer.systemErrorColor }]}>
242
- {traductions.aliasClaimDetailsAlert(
242
+ <ErrorCardWidget
243
+ icon="schedule"
244
+ message={traductions.aliasClaimDetailsAlert(
243
245
  new DateFormat('dd MMMM yyyy', locale).format(claim.dateVerrouillage),
244
246
  new DateFormat('dd MMMM yyyy', locale).format(claim.dateCloture),
245
247
  )}
246
- </Text>
248
+ />
247
249
  )}
248
250
  </ScrollView>
249
251
 
@@ -292,6 +294,5 @@ const styles = StyleSheet.create({
292
294
  card: { paddingVertical: 10, paddingHorizontal: 15 },
293
295
  detail: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
294
296
  detailValue: { flex: 1, textAlign: 'right' },
295
- warning: { marginLeft: 8, marginRight: 20, marginTop: 2, marginBottom: 5 },
296
297
  actions: { flexDirection: 'row', paddingVertical: 10, paddingHorizontal: 10 },
297
298
  });
@@ -1,5 +1,5 @@
1
1
  import { MaterialIcons } from '@expo/vector-icons';
2
- import { Pressable, StyleSheet, Text, useWindowDimensions, View } from 'react-native';
2
+ import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native';
3
3
 
4
4
  import { useBlocBuilder, useCreateBloc } from '../../../../core/bloc_react';
5
5
  import { Di } from '../../../../core/di';
@@ -46,7 +46,6 @@ function CopyButton({ value }: { value: string }) {
46
46
  /** Informations du compte actif : type, numéro et alias. */
47
47
  export function CompteDetailsWidget() {
48
48
  const traductions = useAppLocalizations();
49
- const theme = useTheme();
50
49
  const { width } = useWindowDimensions();
51
50
  const bloc = useCreateBloc(() => {
52
51
  const created = new CompteDetailsBloc(Di.getCompteInputPort());
@@ -57,8 +56,7 @@ export function CompteDetailsWidget() {
57
56
 
58
57
  return (
59
58
  <View>
60
- <Text style={theme.textTheme.titleSmall}>{traductions.compteDetailsPageTitle}</Text>
61
- <View style={styles.gap24} />
59
+ <View style={styles.gap8} />
62
60
  {state instanceof GetCompteDetailsSuccessState && (
63
61
  <View>
64
62
  {/* Type de compte */}
@@ -113,6 +111,7 @@ export function CompteDetailsWidget() {
113
111
  }
114
112
 
115
113
  const styles = StyleSheet.create({
114
+ gap8: { height: 8 },
116
115
  copy: { padding: 8, marginRight: -8 },
117
116
  skeleton: { paddingVertical: 8 },
118
117
  gap16: { height: 16 },
@@ -1,27 +1,35 @@
1
- import { ScrollView, StyleSheet } from 'react-native';
1
+ import { ScrollView, StyleSheet, View } from 'react-native';
2
2
 
3
- import { AppRouter, useAppRouter } from '../../../../../core/router';
4
- import { AppBar, Scaffold } from '../../../../../shared/material/scaffold';
3
+ import { useAppLocalizations } from '../../../../../l10n/localizations';
4
+ import { Scaffold } from '../../../../../shared/material/scaffold';
5
5
  import { MyPageContainer } from '../../../../../shared/widgets/my_page_container';
6
6
  import { AliasDeleteBtnWidget } from '../../../../alias/presentation/pages/alias_delete_btn_widget';
7
7
  import { CompteDetailsWidget } from '../../../../compte/presentation/pages/compte_details_widget';
8
+ import { ProfileAppBar } from '../profile_app_bar';
9
+ import { ProfileSectionTitre } from '../profile_menu';
8
10
 
9
11
  /** Compte du client et suppression de l'alias (route `profileCompte`). */
10
12
  export function ProfileComptePage() {
11
- const router = useAppRouter();
13
+ const traductions = useAppLocalizations();
12
14
  return (
13
- <Scaffold appBar={<AppBar onBack={() => AppRouter.pop(router)} />}>
15
+ <Scaffold appBar={<ProfileAppBar titre={traductions.compteDetailsPageTitle} />}>
14
16
  <MyPageContainer>
15
17
  <ScrollView style={styles.flex}>
16
18
  <CompteDetailsWidget />
17
19
  </ScrollView>
20
+ <View style={styles.gap24} />
21
+
22
+ {/* Suppression de l'alias */}
23
+ <ProfileSectionTitre titre={traductions.comptePageSectionZoneSensible} />
24
+ <AliasDeleteBtnWidget />
25
+ <View style={styles.gap16} />
18
26
  </MyPageContainer>
19
- {/* Bouton supprimer l'alias */}
20
- <AliasDeleteBtnWidget />
21
27
  </Scaffold>
22
28
  );
23
29
  }
24
30
 
25
31
  const styles = StyleSheet.create({
26
32
  flex: { flex: 1 },
33
+ gap16: { height: 16 },
34
+ gap24: { height: 24 },
27
35
  });
@@ -1,20 +1,21 @@
1
- import { ScrollView, StyleSheet, Text, View } from 'react-native';
1
+ import { ScrollView, StyleSheet, View } from 'react-native';
2
2
 
3
3
  import { Images } from '../../../../../core/assets';
4
4
  import { useBloc, useBlocBuilder } from '../../../../../core/bloc_react';
5
5
  import { Di } from '../../../../../core/di';
6
- import { AppRouter, useAppRouter } from '../../../../../core/router';
6
+ import { AppRouter } from '../../../../../core/router';
7
7
  import { SoundManager } from '../../../../../core/sounds';
8
- import { Themer, Themes, useTheme } from '../../../../../core/theme';
8
+ import { Themer, Themes } from '../../../../../core/theme';
9
9
  import type { AppLocalizations } from '../../../../../l10n/app_localizations';
10
10
  import { useAppLocalizations } from '../../../../../l10n/localizations';
11
- import { AppBar, Scaffold } from '../../../../../shared/material/scaffold';
11
+ import { Scaffold } from '../../../../../shared/material/scaffold';
12
12
  import { LoadingPage } from '../../../../../shared/widgets/loading_page';
13
13
  import { MyPageContainer } from '../../../../../shared/widgets/my_page_container';
14
14
  import { ConfigBloc } from '../../../../config/adapters/ui/bloc/config_bloc';
15
15
  import { ConfigLoadedState } from '../../../../config/adapters/ui/bloc/config_state';
16
16
  import { ConfigKey } from '../../../../config/domain/models/config_keys';
17
17
  import type { ConfigParams } from '../../../../config/domain/models/config_params';
18
+ import { ProfileAppBar } from '../profile_app_bar';
18
19
  import { ProfilePageMenu, ProfileItem } from '../profile_menu';
19
20
  import { ProfileParametrePageQRcode } from './profile_parametre_page_qrcode';
20
21
 
@@ -69,8 +70,6 @@ function menuItems(traductions: AppLocalizations, configParams: ConfigParams): P
69
70
  /** Paramètres de l'application (route `profileParametre`). */
70
71
  export function ProfileParametrePage() {
71
72
  const traductions = useAppLocalizations();
72
- const theme = useTheme();
73
- const router = useAppRouter();
74
73
  const configBloc = useBloc(ConfigBloc);
75
74
  const state = useBlocBuilder(
76
75
  configBloc,
@@ -83,15 +82,15 @@ export function ProfileParametrePage() {
83
82
  );
84
83
 
85
84
  return (
86
- <Scaffold appBar={<AppBar onBack={() => AppRouter.pop(router)} />}>
85
+ <Scaffold appBar={<ProfileAppBar titre={traductions.appSettingPageTitle} />}>
87
86
  {!(state instanceof ConfigLoadedState) ? (
88
87
  <LoadingPage />
89
88
  ) : (
90
89
  <MyPageContainer>
91
90
  <ScrollView>
92
- <Text style={[theme.textTheme.titleSmall, styles.title]}>{traductions.appSettingPageTitle}</Text>
93
- <View style={styles.gap24} />
94
- <ProfilePageMenu items={menuItems(traductions, state.configParams)} />
91
+ <View style={styles.gap8} />
92
+ {/* Préférences */}
93
+ <ProfilePageMenu titre={traductions.appSettingPageSectionPreferences} items={menuItems(traductions, state.configParams)} />
95
94
  </ScrollView>
96
95
  </MyPageContainer>
97
96
  )}
@@ -100,6 +99,5 @@ export function ProfileParametrePage() {
100
99
  }
101
100
 
102
101
  const styles = StyleSheet.create({
103
- title: { fontSize: 26 },
104
- gap24: { height: 24 },
102
+ gap8: { height: 8 },
105
103
  });
@@ -1,4 +1,3 @@
1
- import { MaterialIcons } from '@expo/vector-icons';
2
1
  import { Image } from 'expo-image';
3
2
  import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
4
3
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -10,12 +9,13 @@ import { AppRouter, useAppRouter } from '../../../../../core/router';
10
9
  import { Themer, useTheme } from '../../../../../core/theme';
11
10
  import { useAppLocalizations } from '../../../../../l10n/localizations';
12
11
  import { Card } from '../../../../../shared/material/card';
13
- import { MenuActionItem, MenuActionsWidget } from '../../../../../shared/widgets/menu_actions_widget';
12
+ import { AppBar } from '../../../../../shared/material/scaffold';
14
13
  import { ConfigBloc } from '../../../../config/adapters/ui/bloc/config_bloc';
15
14
  import { ConfigChangeEvent } from '../../../../config/adapters/ui/bloc/config_event';
16
15
  import { ConfigLoadedState } from '../../../../config/adapters/ui/bloc/config_state';
17
16
  import { ConfigKey } from '../../../../config/domain/models/config_keys';
18
- import { SettingToggle } from './settings_widgets';
17
+ import { ProfileMenuTile, ProfilePageMenu, ProfileSectionTitre } from '../profile_menu';
18
+ import { SwitchThemed } from './settings_widgets';
19
19
 
20
20
  const NOTIFICATION_KEYS = [
21
21
  ConfigKey.notificationStatus,
@@ -58,88 +58,107 @@ export function ProfileParametrePageNotifications() {
58
58
  },
59
59
  ];
60
60
 
61
+ const basculerNotifications = () =>
62
+ configBloc.add(new ConfigChangeEvent(ConfigKey.notificationStatus, enabled ? 'disabled' : 'enabled'));
63
+ const basculerVibration = () =>
64
+ configBloc.add(new ConfigChangeEvent(ConfigKey.notificationAlertVibration, vibrationEnabled ? 'disabled' : 'enabled'));
65
+
61
66
  return (
62
67
  <View style={[styles.flex, { paddingBottom: insets.bottom }]}>
63
- <View style={styles.backRow}>
64
- <Pressable accessibilityRole="button" hitSlop={12} style={styles.back} onPress={() => AppRouter.pop(router)}>
65
- <MaterialIcons name="arrow-back" size={24} color={theme.colorScheme.onSurface} />
66
- </Pressable>
67
- </View>
68
+ {/* Titre dans la barre, comme les autres pages (dans la feuille : fond de la feuille) */}
69
+ <AppBar
70
+ backgroundColor="transparent"
71
+ onBack={() => AppRouter.pop(router)}
72
+ title={
73
+ <Text numberOfLines={1} style={theme.textTheme.headlineMedium}>
74
+ {traductions.appSettingPageMenuNotificationTitle}
75
+ </Text>
76
+ }
77
+ />
68
78
  <ScrollView style={styles.flex} contentContainerStyle={styles.content}>
69
- <Text style={theme.textTheme.headlineMedium}>{traductions.appSettingPageMenuNotificationTitle}</Text>
70
- <View style={styles.gap20} />
71
79
  {/* Activer les notifications */}
72
- <SettingToggle
73
- icon={Images.iconsCloche}
74
- title={traductions.notificationPageTitle}
75
- value={enabled}
76
- onChange={() => configBloc.add(new ConfigChangeEvent(ConfigKey.notificationStatus, enabled ? 'disabled' : 'enabled'))}
80
+ <ProfilePageMenu
81
+ items={[
82
+ <ProfileMenuTile
83
+ key="notifications"
84
+ image={Images.iconsCloche}
85
+ titre={traductions.notificationPageTitle}
86
+ onPress={basculerNotifications}
87
+ trailing={<SwitchThemed value={enabled} onChange={basculerNotifications} />}
88
+ />,
89
+ ]}
77
90
  />
91
+
78
92
  {/* Style de notification */}
79
- <View style={styles.gap10} />
80
- <Text style={theme.textTheme.headlineMedium}>{traductions.appSettingPageMenuNotificationStyleTitle}</Text>
81
- <View style={styles.gap10} />
82
- <Card>
83
- <View style={styles.wrap}>
84
- {styles_.map((item) => {
85
- const active = (!enabled && item.style == null) || currentStyle === item.style;
93
+ <View style={styles.gap24} />
94
+ <ProfileSectionTitre titre={traductions.appSettingPageMenuNotificationStyleTitle} />
95
+ <Card style={styles.card}>
96
+ <View style={styles.row}>
97
+ {styles_.map((item, i) => {
98
+ // « Aucun » quand les notifications sont désactivées
99
+ const choisi = enabled ? currentStyle === item.style : item.style == null;
86
100
  return (
87
101
  <Pressable
88
102
  key={item.title}
89
103
  accessibilityRole="button"
104
+ accessibilityState={{ selected: choisi }}
90
105
  disabled={!(enabled && item.style != null && currentStyle !== item.style)}
91
106
  onPress={() => configBloc.add(new ConfigChangeEvent(ConfigKey.notificationStyle, item.style!))}
92
- style={styles.styleItem}
107
+ style={[styles.style, i > 0 ? styles.styleGap : null]}
93
108
  >
94
- <Image
95
- source={item.icon}
96
- style={[styles.stylePreview, { backgroundColor: theme.colorScheme.surface }]}
97
- contentFit="cover"
98
- tintColor={active ? 'black' : undefined}
99
- />
100
- <View style={styles.gap10} />
101
- <View style={[styles.badge, { backgroundColor: active ? theme.colorScheme.onSurface : theme.colorScheme.secondary }]}>
102
- <Text style={[theme.textTheme.displayMedium, { color: active ? theme.colorScheme.surface : theme.colorScheme.onSecondary }]}>
103
- {item.title}
104
- </Text>
109
+ {/* Aperçu, entouré quand il est choisi */}
110
+ <View style={[styles.cadre, { borderColor: choisi ? theme.colorScheme.primary : 'transparent' }]}>
111
+ <Image
112
+ source={item.icon}
113
+ style={[styles.apercu, { backgroundColor: theme.colorScheme.surface }]}
114
+ contentFit="cover"
115
+ />
105
116
  </View>
117
+ <Text
118
+ style={[
119
+ theme.textTheme.displayMedium,
120
+ styles.libelle,
121
+ { fontWeight: choisi ? '700' : '500' },
122
+ choisi ? null : { color: Themer.neural03Color },
123
+ ]}
124
+ >
125
+ {item.title}
126
+ </Text>
106
127
  </Pressable>
107
128
  );
108
129
  })}
109
130
  </View>
110
131
  </Card>
132
+ {/* Description des styles de notification */}
111
133
  <View style={styles.gap10} />
112
- <Text style={[theme.textTheme.displayLarge, { color: Themer.neural04Color }]}>
134
+ <Text style={[theme.textTheme.displaySmall, styles.description, { color: Themer.neural03Color }]}>
113
135
  {traductions.appSettingPageMenuNotificationStyleDialogDesc}
114
136
  </Text>
115
- {/* Son */}
116
- <View style={styles.gap20} />
117
- <Text style={theme.textTheme.headlineMedium}>{traductions.appSettingPageMenuNotificationSonTitle}</Text>
118
- <View style={styles.gap10} />
119
- <MenuActionsWidget
137
+
138
+ {/* Son et vibration */}
139
+ <View style={styles.gap24} />
140
+ <ProfilePageMenu
141
+ titre={traductions.appSettingPageMenuNotificationSonTitle}
120
142
  items={[
121
- new MenuActionItem(
122
- Images.iconsCloche,
123
- traductions.notificationPageTitle,
124
- sonEnabled ? traductions.statutActive : traductions.statutDesactive,
125
- () => void AppRouter.push(router, AppRouter.profileParametreNotificationsSon),
126
- { iconSize: 20 },
127
- ),
143
+ // Son des notifications
144
+ <ProfileMenuTile
145
+ key="son"
146
+ image={Images.iconsCloche}
147
+ titre={traductions.notificationPageTitle}
148
+ sousTitre={sonEnabled ? traductions.statutActive : traductions.statutDesactive}
149
+ onPress={() => void AppRouter.push(router, AppRouter.profileParametreNotificationsSon)}
150
+ />,
151
+ // Vibration des notifications
152
+ <ProfileMenuTile
153
+ key="vibration"
154
+ icone="vibration"
155
+ titre={traductions.appSettingPageMenuNotificationVibrTitle}
156
+ sousTitre={traductions.appSettingPageMenuNotificationVibrSubtitle}
157
+ onPress={basculerVibration}
158
+ trailing={<SwitchThemed value={vibrationEnabled} onChange={basculerVibration} />}
159
+ />,
128
160
  ]}
129
161
  />
130
- {/* Vibration */}
131
- <View style={styles.gap20} />
132
- <Text style={theme.textTheme.headlineMedium}>{traductions.appSettingPageMenuNotificationVibrTitle}</Text>
133
- <View style={styles.gap10} />
134
- <SettingToggle
135
- icon={Images.iconsCloche}
136
- title={traductions.appSettingPageMenuNotificationVibrTitle}
137
- subtitle={traductions.appSettingPageMenuNotificationVibrSubtitle}
138
- value={vibrationEnabled}
139
- onChange={() =>
140
- configBloc.add(new ConfigChangeEvent(ConfigKey.notificationAlertVibration, vibrationEnabled ? 'disabled' : 'enabled'))
141
- }
142
- />
143
162
  </ScrollView>
144
163
  </View>
145
164
  );
@@ -147,13 +166,15 @@ export function ProfileParametrePageNotifications() {
147
166
 
148
167
  const styles = StyleSheet.create({
149
168
  flex: { flex: 1 },
150
- backRow: { flexDirection: 'row' },
151
- back: { padding: 12 },
152
- content: { paddingTop: 12, paddingHorizontal: 12, paddingBottom: 20 },
153
- wrap: { padding: 20, flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', rowGap: 10, columnGap: 10 },
154
- styleItem: { alignItems: 'center' },
155
- stylePreview: { width: 60, height: 120, borderRadius: 8 },
156
- badge: { borderRadius: 10, paddingHorizontal: 10, paddingVertical: 5 },
169
+ content: { paddingTop: 8, paddingHorizontal: 20, paddingBottom: 24 },
170
+ card: { margin: 0 },
171
+ row: { flexDirection: 'row', alignItems: 'flex-start', padding: 14 },
172
+ style: { flex: 1, alignItems: 'center', borderRadius: 14 },
173
+ styleGap: { marginLeft: 12 },
174
+ cadre: { alignSelf: 'stretch', padding: 3, borderWidth: 2, borderRadius: 14 },
175
+ apercu: { width: '100%', aspectRatio: 0.55, borderRadius: 10 },
176
+ libelle: { marginTop: 8, textAlign: 'center' },
177
+ description: { paddingHorizontal: 4 },
157
178
  gap10: { height: 10 },
158
- gap20: { height: 20 },
179
+ gap24: { height: 24 },
159
180
  });