@pi-spi/app 1.6.0 → 2.0.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 (109) hide show
  1. package/README.md +64 -30
  2. package/example/App.tsx +27 -24
  3. package/lib/module/core/assets.js +0 -7
  4. package/lib/module/core/assets.js.map +1 -1
  5. package/lib/module/core/di.js +17 -8
  6. package/lib/module/core/di.js.map +1 -1
  7. package/lib/module/core/pi_spi_custom_theme.js +22 -10
  8. package/lib/module/core/pi_spi_custom_theme.js.map +1 -1
  9. package/lib/module/core/pi_spi_sdk_config.js +22 -8
  10. package/lib/module/core/pi_spi_sdk_config.js.map +1 -1
  11. package/lib/module/core/theme.js +345 -225
  12. package/lib/module/core/theme.js.map +1 -1
  13. package/lib/module/index.js +1 -1
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/module/l10n/app_localizations.js.map +1 -1
  16. package/lib/module/l10n/app_localizations_en.js +14 -0
  17. package/lib/module/l10n/app_localizations_en.js.map +1 -1
  18. package/lib/module/l10n/app_localizations_fr.js +14 -0
  19. package/lib/module/l10n/app_localizations_fr.js.map +1 -1
  20. package/lib/module/l10n/app_localizations_pt.js +14 -0
  21. package/lib/module/l10n/app_localizations_pt.js.map +1 -1
  22. package/lib/module/modules/config/domain/models/config_keys.js +3 -1
  23. package/lib/module/modules/config/domain/models/config_keys.js.map +1 -1
  24. package/lib/module/modules/config/domain/services/config_service.js +31 -2
  25. package/lib/module/modules/config/domain/services/config_service.js.map +1 -1
  26. package/lib/module/modules/home/presentation/pages/home_tab_compte.js +0 -1
  27. package/lib/module/modules/home/presentation/pages/home_tab_compte.js.map +1 -1
  28. package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js +38 -37
  29. package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js.map +1 -1
  30. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js +2 -2
  31. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js.map +1 -1
  32. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.js +2 -2
  33. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.js.map +1 -1
  34. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js +135 -199
  35. package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js.map +1 -1
  36. package/lib/module/modules/profile/presentation/pages/profile_menu.js +4 -3
  37. package/lib/module/modules/profile/presentation/pages/profile_menu.js.map +1 -1
  38. package/lib/module/modules/transactions/presentation/pages/transaction_details/transaction_details_page_recu.js +3 -1
  39. package/lib/module/modules/transactions/presentation/pages/transaction_details/transaction_details_page_recu.js.map +1 -1
  40. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js +5 -4
  41. package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js.map +1 -1
  42. package/lib/module/modules/transactions/presentation/pages/transaction_split/transaction_split_page_repartition.js +2 -2
  43. package/lib/module/modules/transactions/presentation/pages/transaction_split/transaction_split_page_repartition.js.map +1 -1
  44. package/lib/module/pi_spi_sdk_entry.js +7 -2
  45. package/lib/module/pi_spi_sdk_entry.js.map +1 -1
  46. package/lib/module/shared/widgets/custom_progressbar.js +3 -2
  47. package/lib/module/shared/widgets/custom_progressbar.js.map +1 -1
  48. package/lib/module/shared/widgets/selection.js +1 -1
  49. package/lib/module/shared/widgets/selection.js.map +1 -1
  50. package/lib/typescript/src/core/assets.d.ts +0 -7
  51. package/lib/typescript/src/core/assets.d.ts.map +1 -1
  52. package/lib/typescript/src/core/di.d.ts.map +1 -1
  53. package/lib/typescript/src/core/pi_spi_custom_theme.d.ts +17 -8
  54. package/lib/typescript/src/core/pi_spi_custom_theme.d.ts.map +1 -1
  55. package/lib/typescript/src/core/pi_spi_sdk_config.d.ts +22 -8
  56. package/lib/typescript/src/core/pi_spi_sdk_config.d.ts.map +1 -1
  57. package/lib/typescript/src/core/theme.d.ts +123 -53
  58. package/lib/typescript/src/core/theme.d.ts.map +1 -1
  59. package/lib/typescript/src/index.d.ts +1 -1
  60. package/lib/typescript/src/index.d.ts.map +1 -1
  61. package/lib/typescript/src/l10n/app_localizations.d.ts +28 -0
  62. package/lib/typescript/src/l10n/app_localizations.d.ts.map +1 -1
  63. package/lib/typescript/src/l10n/app_localizations_en.d.ts.map +1 -1
  64. package/lib/typescript/src/l10n/app_localizations_fr.d.ts.map +1 -1
  65. package/lib/typescript/src/l10n/app_localizations_pt.d.ts.map +1 -1
  66. package/lib/typescript/src/modules/config/domain/models/config_keys.d.ts +3 -1
  67. package/lib/typescript/src/modules/config/domain/models/config_keys.d.ts.map +1 -1
  68. package/lib/typescript/src/modules/config/domain/services/config_service.d.ts +7 -0
  69. package/lib/typescript/src/modules/config/domain/services/config_service.d.ts.map +1 -1
  70. package/lib/typescript/src/modules/home/presentation/pages/home_tab_compte_more_menu.d.ts +2 -0
  71. package/lib/typescript/src/modules/home/presentation/pages/home_tab_compte_more_menu.d.ts.map +1 -1
  72. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page.d.ts.map +1 -1
  73. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.d.ts +1 -1
  74. package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.d.ts.map +1 -1
  75. package/lib/typescript/src/shared/widgets/custom_progressbar.d.ts.map +1 -1
  76. package/lib/typescript/src/shared/widgets/selection.d.ts +1 -1
  77. package/lib/typescript/src/shared/widgets/selection.d.ts.map +1 -1
  78. package/package.json +1 -1
  79. package/src/core/assets.ts +0 -7
  80. package/src/core/di.ts +13 -8
  81. package/src/core/pi_spi_custom_theme.ts +22 -15
  82. package/src/core/pi_spi_sdk_config.ts +27 -9
  83. package/src/core/theme.ts +381 -269
  84. package/src/index.ts +1 -1
  85. package/src/l10n/app_localizations.ts +28 -0
  86. package/src/l10n/app_localizations_en.ts +14 -0
  87. package/src/l10n/app_localizations_fr.ts +14 -0
  88. package/src/l10n/app_localizations_pt.ts +14 -0
  89. package/src/modules/config/domain/models/config_keys.ts +3 -1
  90. package/src/modules/config/domain/services/config_service.ts +31 -2
  91. package/src/modules/home/presentation/pages/home_tab_compte.tsx +1 -1
  92. package/src/modules/home/presentation/pages/home_tab_compte_more_menu.tsx +34 -42
  93. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page.tsx +2 -5
  94. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.tsx +2 -2
  95. package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.tsx +105 -148
  96. package/src/modules/profile/presentation/pages/profile_menu.tsx +2 -2
  97. package/src/modules/transactions/presentation/pages/transaction_details/transaction_details_page_recu.tsx +3 -1
  98. package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.tsx +4 -4
  99. package/src/modules/transactions/presentation/pages/transaction_split/transaction_split_page_repartition.tsx +2 -2
  100. package/src/pi_spi_sdk_entry.tsx +9 -2
  101. package/src/shared/widgets/custom_progressbar.tsx +3 -2
  102. package/src/shared/widgets/selection.tsx +1 -1
  103. package/assets/images/theme_light_blue.jpeg +0 -0
  104. package/assets/images/theme_light_green.jpeg +0 -0
  105. package/assets/images/theme_light_orange.jpeg +0 -0
  106. package/assets/images/theme_light_pink.jpeg +0 -0
  107. package/assets/images/theme_light_purple.jpeg +0 -0
  108. package/assets/images/theme_light_red.jpeg +0 -0
  109. package/assets/images/theme_light_yellow.jpeg +0 -0
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ export {
22
22
  type PiSpiLogLevel,
23
23
  } from './core/pi_spi_sdk_config';
24
24
  export { PiSpiCustomTheme } from './core/pi_spi_custom_theme';
25
- export { Themes, type ThemeData } from './core/theme';
25
+ export { Accents, Themes, type ThemeData } from './core/theme';
26
26
  export {
27
27
  type AppLocalizations,
28
28
  type SupportedLocaleCode,
@@ -915,6 +915,8 @@ export interface AppLocalizations {
915
915
  readonly transactionDetailsCancelRejectMessage: string;
916
916
  /** Send receipt */
917
917
  readonly transactionDetailsRecuTitle: string;
918
+ /** Issued on: */
919
+ readonly transactionDetailsRecuPdfEditeLe: string;
918
920
  /** You can download or share the PDF file */
919
921
  readonly transactionDetailsRecuSubTitle: string;
920
922
  /** Identifier */
@@ -1215,6 +1217,32 @@ export interface AppLocalizations {
1215
1217
  readonly appSettingPageMenuThemeDefault: string;
1216
1218
  /** Apparence */
1217
1219
  readonly appSettingPageMenuThemePageTitle: string;
1220
+ /** Theme */
1221
+ readonly appSettingPageThemeSection: string;
1222
+ /** Accent colour */
1223
+ readonly appSettingPageAccentSection: string;
1224
+ /** Default */
1225
+ readonly appSettingPageAccentDefaut: string;
1226
+ /** Green */
1227
+ readonly appSettingPageAccentVert: string;
1228
+ /** Blue */
1229
+ readonly appSettingPageAccentBleu: string;
1230
+ /** Red */
1231
+ readonly appSettingPageAccentRouge: string;
1232
+ /** Orange */
1233
+ readonly appSettingPageAccentOrange: string;
1234
+ /** Purple */
1235
+ readonly appSettingPageAccentViolet: string;
1236
+ /** Pink */
1237
+ readonly appSettingPageAccentRose: string;
1238
+ /** Turquoise */
1239
+ readonly appSettingPageAccentTurquoise: string;
1240
+ /** Terracotta */
1241
+ readonly appSettingPageAccentTerracotta: string;
1242
+ /** Burgundy */
1243
+ readonly appSettingPageAccentBordeaux: string;
1244
+ /** Slate */
1245
+ readonly appSettingPageAccentArdoise: string;
1218
1246
  /** My QR code by default */
1219
1247
  readonly appSettingPageMenuQrCodeTitle: string;
1220
1248
  /** Enable to display your QR Code by default */
@@ -464,6 +464,7 @@ If this request comes from someone you trust, simply add their alias to your con
464
464
  transactionDetailsCancelReasonLabel: `Reason`,
465
465
  transactionDetailsCancelRejectMessage: `The cancellation request has been successfully rejected`,
466
466
  transactionDetailsRecuTitle: `Send receipt`,
467
+ transactionDetailsRecuPdfEditeLe: `Issued on:`,
467
468
  transactionDetailsRecuSubTitle: `You can download or share the PDF file`,
468
469
  transactionDetailsRecuInfoIdentifiant: `Identifier`,
469
470
  transactionDetailsRecuInfoReference: `Reference`,
@@ -619,6 +620,19 @@ Scanning will be automatic`,
619
620
  appSettingPageMenuThemeCustom: `App theme`,
620
621
  appSettingPageMenuThemeDefault: `System`,
621
622
  appSettingPageMenuThemePageTitle: `Apparence`,
623
+ appSettingPageThemeSection: `Theme`,
624
+ appSettingPageAccentSection: `Accent colour`,
625
+ appSettingPageAccentDefaut: `Default`,
626
+ appSettingPageAccentVert: `Green`,
627
+ appSettingPageAccentBleu: `Blue`,
628
+ appSettingPageAccentRouge: `Red`,
629
+ appSettingPageAccentOrange: `Orange`,
630
+ appSettingPageAccentViolet: `Purple`,
631
+ appSettingPageAccentRose: `Pink`,
632
+ appSettingPageAccentTurquoise: `Turquoise`,
633
+ appSettingPageAccentTerracotta: `Terracotta`,
634
+ appSettingPageAccentBordeaux: `Burgundy`,
635
+ appSettingPageAccentArdoise: `Slate`,
622
636
  appSettingPageMenuQrCodeTitle: `My QR code by default`,
623
637
  appSettingPageMenuQrCodeSelectTitle: `Enable to display your QR Code by default`,
624
638
  appSettingPageMenuQrCodeDeselectTitle: `Disable to display the camera by default`,
@@ -464,6 +464,7 @@ Si cette demande provient d'une personne de confiance, il vous suffit d'ajouter
464
464
  transactionDetailsCancelReasonLabel: `Raison`,
465
465
  transactionDetailsCancelRejectMessage: `La demande d’annulation est rejetée avec succès`,
466
466
  transactionDetailsRecuTitle: `Reçu de l'envoi`,
467
+ transactionDetailsRecuPdfEditeLe: `Edité le :`,
467
468
  transactionDetailsRecuSubTitle: `Vous pouvez partager ou télécharger le PDF`,
468
469
  transactionDetailsRecuInfoIdentifiant: `Identifiant`,
469
470
  transactionDetailsRecuInfoReference: `Référence`,
@@ -618,6 +619,19 @@ Le scan se fait automatiquement`,
618
619
  appSettingPageMenuThemeCustom: `Thème de l'application`,
619
620
  appSettingPageMenuThemeDefault: `Système`,
620
621
  appSettingPageMenuThemePageTitle: `Apparence`,
622
+ appSettingPageThemeSection: `Thème`,
623
+ appSettingPageAccentSection: `Couleur d'accentuation`,
624
+ appSettingPageAccentDefaut: `Défaut`,
625
+ appSettingPageAccentVert: `Vert`,
626
+ appSettingPageAccentBleu: `Bleu`,
627
+ appSettingPageAccentRouge: `Rouge`,
628
+ appSettingPageAccentOrange: `Orange`,
629
+ appSettingPageAccentViolet: `Violet`,
630
+ appSettingPageAccentRose: `Rose`,
631
+ appSettingPageAccentTurquoise: `Turquoise`,
632
+ appSettingPageAccentTerracotta: `Terracotta`,
633
+ appSettingPageAccentBordeaux: `Bordeaux`,
634
+ appSettingPageAccentArdoise: `Ardoise`,
621
635
  appSettingPageMenuQrCodeTitle: `Mon QR code par défaut`,
622
636
  appSettingPageMenuQrCodeSelectTitle: `Activer pour afficher votre QR Code par défaut`,
623
637
  appSettingPageMenuQrCodeDeselectTitle: `Désactiver pour afficher la caméra par défaut`,
@@ -464,6 +464,7 @@ Se este pedido for de uma pessoa de confiança, basta adicionar o alias aos seus
464
464
  transactionDetailsCancelReasonLabel: `Razão`,
465
465
  transactionDetailsCancelRejectMessage: `A demanda de cancelamento foi rejeitada com sucesso`,
466
466
  transactionDetailsRecuTitle: `Recebido de envio`,
467
+ transactionDetailsRecuPdfEditeLe: `Emitido em:`,
467
468
  transactionDetailsRecuSubTitle: `Você pode compartilhar ou baixar o PDF`,
468
469
  transactionDetailsRecuInfoIdentifiant: `Identificador`,
469
470
  transactionDetailsRecuInfoReference: `Referência`,
@@ -619,6 +620,19 @@ O scan é feito automaticamente`,
619
620
  appSettingPageMenuThemeCustom: `Tema da aplicação`,
620
621
  appSettingPageMenuThemeDefault: `Sistema`,
621
622
  appSettingPageMenuThemePageTitle: `Aparência`,
623
+ appSettingPageThemeSection: `Tema`,
624
+ appSettingPageAccentSection: `Cor de destaque`,
625
+ appSettingPageAccentDefaut: `Padrão`,
626
+ appSettingPageAccentVert: `Verde`,
627
+ appSettingPageAccentBleu: `Azul`,
628
+ appSettingPageAccentRouge: `Vermelho`,
629
+ appSettingPageAccentOrange: `Laranja`,
630
+ appSettingPageAccentViolet: `Roxo`,
631
+ appSettingPageAccentRose: `Rosa`,
632
+ appSettingPageAccentTurquoise: `Turquesa`,
633
+ appSettingPageAccentTerracotta: `Terracota`,
634
+ appSettingPageAccentBordeaux: `Bordô`,
635
+ appSettingPageAccentArdoise: `Ardósia`,
622
636
  appSettingPageMenuQrCodeTitle: `Meu QR Code por defeito`,
623
637
  appSettingPageMenuQrCodeSelectTitle: `Ativar para exibir o meu QR Code por defeito`,
624
638
  appSettingPageMenuQrCodeDeselectTitle: `Desativar para exibir a câmera por defeito`,
@@ -6,8 +6,10 @@ export enum ConfigKey {
6
6
  /** l'utilisateur a passé l'introduction */
7
7
  introductionPassed = 'INTRODUCTION_PASSED',
8
8
 
9
- /** theme de l'utilisateur */
9
+ /** theme de l'utilisateur : système, clair ou sombre */
10
10
  preferedTheme = 'PREFERED_THEME',
11
+ /** Couleur d'accentuation (PI-SPI par défaut) */
12
+ preferedAccent = 'PREFERED_ACCENT',
11
13
 
12
14
  /** Permission notification */
13
15
  permissionNotification = 'PERMISSION_NOTIFICATION',
@@ -1,6 +1,6 @@
1
1
  import type { ConfigInputPort } from '../../ports/input/config_input_port';
2
2
  import type { ConfigOutputPort } from '../../ports/output/config_output_port';
3
- import { configKeyValues } from '../models/config_keys';
3
+ import { ConfigKey, configKeyValues } from '../models/config_keys';
4
4
  import type { ConfigParams } from '../models/config_params';
5
5
 
6
6
  /** Service contenant la logique de gestion des configurations */
@@ -11,9 +11,38 @@ export class ConfigService implements ConfigInputPort {
11
11
  */
12
12
  constructor(readonly configOutputPort: ConfigOutputPort) {}
13
13
 
14
+ /**
15
+ * Anciens thèmes de couleur, remplacés par le thème clair et une couleur
16
+ * d'accentuation (le jaune, trop proche de l'ambre, devient PI-SPI)
17
+ */
18
+ static readonly anciensThemes: Readonly<Record<string, string>> = {
19
+ yellowTheme: 'pispi',
20
+ greenTheme: 'vert',
21
+ blueTheme: 'bleu',
22
+ redTheme: 'rouge',
23
+ orangeTheme: 'orange',
24
+ purpleTheme: 'violet',
25
+ pinkTheme: 'rose',
26
+ // Thème personnalisé de l'hôte : sa couleur devient l'accent
27
+ customTheme: 'application',
28
+ };
29
+
14
30
  recuperer(): ConfigParams {
15
31
  // Récuperer toute la config et Appeler le systeme de stockage
16
- return this.configOutputPort.recuperer([...configKeyValues]);
32
+ const config = this.configOutputPort.recuperer([...configKeyValues]);
33
+ this.migrerTheme(config);
34
+ return config;
35
+ }
36
+
37
+ /** Convertit une seule fois un ancien thème de couleur en thème clair et couleur d'accentuation, et l'enregistre */
38
+ private migrerTheme(config: ConfigParams): void {
39
+ const ancien = config.params[ConfigKey.preferedTheme];
40
+ const accent = ancien != null ? ConfigService.anciensThemes[ancien] : undefined;
41
+ if (accent == null) return;
42
+ config.params[ConfigKey.preferedTheme] = 'lightTheme';
43
+ config.params[ConfigKey.preferedAccent] = accent;
44
+ void this.configOutputPort.modifier(ConfigKey.preferedTheme, 'lightTheme');
45
+ void this.configOutputPort.modifier(ConfigKey.preferedAccent, accent);
17
46
  }
18
47
 
19
48
  async modifier(key: string, value: string): Promise<void> {
@@ -75,7 +75,7 @@ export function HomeTabCompte() {
75
75
  <CtaWidget
76
76
  icon={(color) => <MaterialIcons name="more-horiz" size={30} color={color} />}
77
77
  label={traductions.homeActionMore}
78
- action={hasAlias ? () => void showModalBottomSheet(router, { themed: true, builder: () => <HomeTabCompteMoreMenu /> }) : null}
78
+ action={hasAlias ? () => void showModalBottomSheet(router, { builder: () => <HomeTabCompteMoreMenu /> }) : null}
79
79
  />
80
80
  </View>
81
81
  <View style={styles.gap14} />
@@ -1,71 +1,63 @@
1
- import { ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native';
2
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
1
+ import { StyleSheet, View } from 'react-native';
3
2
 
4
3
  import { Images } from '../../../../core/assets';
5
4
  import { AppRouter, useAppRouter } from '../../../../core/router';
6
- import { useTheme } from '../../../../core/theme';
7
5
  import { useAppLocalizations } from '../../../../l10n/localizations';
8
- import { Card } from '../../../../shared/material/card';
9
- import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
6
+ import { SelectionSheet } from '../../../../shared/widgets/selection';
7
+ import { ProfileMenuTile, ProfilePageMenu } from '../../../profile/presentation/pages/profile_menu';
10
8
 
11
9
  /**
12
10
  * Menu « Plus » de l'accueil. Le participant peut personnaliser ce menu
13
11
  * pour y ajouter des fonctionnalités propres à son système.
12
+ * Même feuille que celles du profil : à ouvrir avec showModalBottomSheet
13
+ * sans `themed`, la feuille dessine elle-même son fond.
14
14
  */
15
15
  export function HomeTabCompteMoreMenu() {
16
16
  const traductions = useAppLocalizations();
17
- const theme = useTheme();
18
17
  const router = useAppRouter();
19
- const { height } = useWindowDimensions();
20
- const insets = useSafeAreaInsets();
21
18
  // Fermer le menu avant d'ouvrir la page : sinon il reste affiché par-dessus
22
19
  const open = (location: string) => {
23
20
  AppRouter.pop(router);
24
21
  void AppRouter.push(router, location);
25
22
  };
26
23
  return (
27
- <View
28
- style={[
29
- styles.box,
30
- { maxHeight: height * 0.7, backgroundColor: theme.colorScheme.surface, paddingBottom: 20 + insets.bottom },
31
- ]}
32
- >
33
- <Card>
34
- <ScrollView>
35
- <MenuActionsWidget
24
+ <SelectionSheet
25
+ contenu={
26
+ <View style={styles.contenu}>
27
+ <ProfilePageMenu
36
28
  items={[
37
29
  // Programmer un paiement
38
- new MenuActionItem(
39
- Images.homeFooterTransfer,
40
- traductions.homeActionMoreSheetProgrammerTitle,
41
- traductions.homeActionMoreSheetProgrammerSubTitle,
42
- () => open(AppRouter.subscriptionSchedule),
43
- { iconSize: 20 },
44
- ),
30
+ <ProfileMenuTile
31
+ key="programmer"
32
+ image={Images.homeFooterTransfer}
33
+ titre={traductions.homeActionMoreSheetProgrammerTitle}
34
+ sousTitre={traductions.homeActionMoreSheetProgrammerSubTitle}
35
+ onPress={() => open(AppRouter.subscriptionSchedule)}
36
+ />,
45
37
  // Trouver un abonnement
46
- new MenuActionItem(
47
- Images.homeMoreFindSubscription,
48
- traductions.homeActionMoreSheetAbonnementTitle,
49
- traductions.homeActionMoreSheetAbonnementSubTitle,
50
- () => open(AppRouter.subscriptionSubscribe),
51
- { iconSize: 20 },
52
- ),
38
+ <ProfileMenuTile
39
+ key="abonnement"
40
+ image={Images.homeMoreFindSubscription}
41
+ titre={traductions.homeActionMoreSheetAbonnementTitle}
42
+ sousTitre={traductions.homeActionMoreSheetAbonnementSubTitle}
43
+ onPress={() => open(AppRouter.subscriptionSubscribe)}
44
+ />,
53
45
  // Partager dépenses
54
- new MenuActionItem(
55
- Images.homeMoreSplitPayments,
56
- traductions.homeActionMoreSheetPartagerTitle,
57
- traductions.homeActionMoreSheetPartagerSubTitle,
58
- () => open(AppRouter.transactionSplitPaymentTx),
59
- { iconSize: 20 },
60
- ),
46
+ <ProfileMenuTile
47
+ key="partager"
48
+ image={Images.homeMoreSplitPayments}
49
+ titre={traductions.homeActionMoreSheetPartagerTitle}
50
+ sousTitre={traductions.homeActionMoreSheetPartagerSubTitle}
51
+ onPress={() => open(AppRouter.transactionSplitPaymentTx)}
52
+ />,
61
53
  ]}
62
54
  />
63
- </ScrollView>
64
- </Card>
65
- </View>
55
+ </View>
56
+ }
57
+ />
66
58
  );
67
59
  }
68
60
 
69
61
  const styles = StyleSheet.create({
70
- box: { borderRadius: 20, paddingTop: 20, paddingHorizontal: 20 },
62
+ contenu: { paddingHorizontal: 16, paddingTop: 8, paddingBottom: 20 },
71
63
  });
@@ -46,10 +46,7 @@ function menuItems(traductions: AppLocalizations, configParams: ConfigParams): P
46
46
  if (Di.config.appConfig.canChangeTheme) {
47
47
  items.push(
48
48
  new ProfileItem(Images.iconsTheme, traductions.appSettingPageMenuThemeTitle, AppRouter.profileParametreTheme, {
49
- subtitle:
50
- params[ConfigKey.preferedTheme] === Themer.customThemeCode
51
- ? traductions.appSettingPageMenuThemeCustom
52
- : Themes.label(theme, traductions),
49
+ subtitle: `${Themes.label(theme, traductions)} · ${Themer.accentLabel(traductions, params[ConfigKey.preferedAccent])}`,
53
50
  }),
54
51
  );
55
52
  }
@@ -76,7 +73,7 @@ export function ProfileParametrePage() {
76
73
  (_p, current) =>
77
74
  current instanceof ConfigLoadedState &&
78
75
  current.updatedKey != null &&
79
- [ConfigKey.showQRcode, ConfigKey.preferedTheme, ConfigKey.notificationStatus, ConfigKey.notificationAlertSound].includes(
76
+ [ConfigKey.showQRcode, ConfigKey.preferedTheme, ConfigKey.preferedAccent, ConfigKey.notificationStatus, ConfigKey.notificationAlertSound].includes(
80
77
  current.updatedKey,
81
78
  ),
82
79
  );
@@ -4,7 +4,7 @@ import { Images } from '../../../../../core/assets';
4
4
  import { useBloc, useBlocBuilder } from '../../../../../core/bloc_react';
5
5
  import { Themer, useTheme } from '../../../../../core/theme';
6
6
  import { useAppLocalizations } from '../../../../../l10n/localizations';
7
- import { SbSelectionSheet } from '../../../../../shared/widgets/selection';
7
+ import { SelectionSheet } from '../../../../../shared/widgets/selection';
8
8
  import { ConfigBloc } from '../../../../config/adapters/ui/bloc/config_bloc';
9
9
  import { ConfigChangeEvent } from '../../../../config/adapters/ui/bloc/config_event';
10
10
  import { ConfigLoadedState } from '../../../../config/adapters/ui/bloc/config_state';
@@ -25,7 +25,7 @@ export function ProfileParametrePageQRcode() {
25
25
  const basculer = () => configBloc.add(new ConfigChangeEvent(ConfigKey.showQRcode, showQRcode ? '0' : '1'));
26
26
 
27
27
  return (
28
- <SbSelectionSheet
28
+ <SelectionSheet
29
29
  titre={traductions.appSettingPageMenuQrCodeTitle}
30
30
  entete={
31
31
  <Text style={[theme.textTheme.displaySmall, { color: Themer.neural03Color }]}>
@@ -1,11 +1,11 @@
1
1
  import { MaterialIcons } from '@expo/vector-icons';
2
2
  import { Image } from 'expo-image';
3
- import { useState, type ReactNode } from 'react';
3
+ import { useState } from 'react';
4
4
  import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
5
5
 
6
6
  import { Images } from '../../../../../core/assets';
7
7
  import { useBloc, useBlocBuilder } from '../../../../../core/bloc_react';
8
- import { Themer, Themes, useTheme, type ThemeData } from '../../../../../core/theme';
8
+ import { Accents, Themer, Themes, useTheme } from '../../../../../core/theme';
9
9
  import type { AppLocalizations } from '../../../../../l10n/app_localizations';
10
10
  import { useAppLocalizations } from '../../../../../l10n/localizations';
11
11
  import { Card } from '../../../../../shared/material/card';
@@ -24,20 +24,6 @@ function themeIcon(theme: Themes): number {
24
24
  return Images.imageThemeLight;
25
25
  case Themes.dark:
26
26
  return Images.imageThemeDark;
27
- case Themes.yellow:
28
- return Images.imageThemeLightYellow;
29
- case Themes.green:
30
- return Images.imageThemeLightGreen;
31
- case Themes.blue:
32
- return Images.imageThemeLightBlue;
33
- case Themes.red:
34
- return Images.imageThemeLightRed;
35
- case Themes.orange:
36
- return Images.imageThemeLightOrange;
37
- case Themes.purple:
38
- return Images.imageThemeLightPurple;
39
- case Themes.pink:
40
- return Images.imageThemeLightPink;
41
27
  default:
42
28
  return Images.imageThemeSystem;
43
29
  }
@@ -47,53 +33,119 @@ function themeTitle(traductions: AppLocalizations, theme: Themes): string {
47
33
  return theme === Themes.system ? traductions.appSettingPageMenuThemeDefault : Themes.label(theme, traductions);
48
34
  }
49
35
 
50
- /** Choix du thème de l'application (route `profileParametreTheme`). */
36
+ /** Choix du thème (système, clair, sombre) et de la couleur d'accentuation (route `profileParametreTheme`). */
51
37
  export function ProfileParametrePageTheme() {
52
38
  const traductions = useAppLocalizations();
39
+ const theme = useTheme();
53
40
  const configBloc = useBloc(ConfigBloc);
54
41
  const state = useBlocBuilder(
55
42
  configBloc,
56
- (_p, current) => current instanceof ConfigLoadedState && current.updatedKey === ConfigKey.preferedTheme,
43
+ (_p, current) =>
44
+ current instanceof ConfigLoadedState &&
45
+ (current.updatedKey === ConfigKey.preferedTheme || current.updatedKey === ConfigKey.preferedAccent),
57
46
  );
58
- const currentTheme = state.configParams.params[ConfigKey.preferedTheme];
59
-
47
+ const currentTheme = Themes.fromCode(state.configParams.params[ConfigKey.preferedTheme]);
48
+ const currentAccent = state.configParams.params[ConfigKey.preferedAccent] ?? Accents.parDefaut;
49
+ const sombre = theme.brightness === 'dark';
60
50
  const [largeurGrille, setLargeurGrille] = useState(0);
51
+
61
52
  // Trois aperçus par ligne
53
+ const espace = 12;
62
54
  // Arrondi vers le bas : sinon la troisième colonne peut passer à la ligne
63
- const largeur = Math.floor((largeurGrille - 2 * ESPACE) / 3);
55
+ const largeur = Math.floor((largeurGrille - 2 * espace) / 3);
64
56
 
65
57
  return (
66
58
  <Scaffold appBar={<ProfileAppBar titre={traductions.appSettingPageMenuThemePageTitle} />}>
67
59
  <MyPageContainer>
68
60
  <ScrollView contentContainerStyle={styles.contenu}>
69
- {/* Thème */}
70
- <ProfileSectionTitre titre={traductions.appSettingPageMenuThemeTitle} />
61
+ {/* Thème : système, clair ou sombre */}
62
+ <ProfileSectionTitre titre={traductions.appSettingPageThemeSection} />
71
63
  <Card style={styles.card}>
72
64
  <View style={styles.grille} onLayout={(e) => setLargeurGrille(e.nativeEvent.layout.width - 2 * 14)}>
73
- {largeurGrille > 0 && (
74
- <>
75
- {/* Thème personnalisé de l'hôte, en tête */}
76
- {Themer.customTheme != null && (
77
- <ThemeOption
78
- largeur={largeur}
79
- title={traductions.appSettingPageMenuThemeCustom}
80
- selected={currentTheme === Themer.customThemeCode}
81
- onSelect={() => configBloc.add(new ConfigChangeEvent(ConfigKey.preferedTheme, Themer.customThemeCode))}
82
- preview={<CustomThemePreview theme={Themer.customTheme} />}
83
- />
84
- )}
85
- {Themes.list().map((item) => (
86
- <ThemeOption
65
+ {largeurGrille > 0 &&
66
+ Themes.list().map((item) => {
67
+ const choisi = currentTheme === item;
68
+ return (
69
+ <Pressable
87
70
  key={item}
88
- largeur={largeur}
89
- title={themeTitle(traductions, item)}
90
- selected={currentTheme === item}
91
- onSelect={() => configBloc.add(new ConfigChangeEvent(ConfigKey.preferedTheme, item))}
92
- preview={<Image source={themeIcon(item)} style={styles.flex} contentFit="cover" />}
93
- />
94
- ))}
95
- </>
96
- )}
71
+ accessibilityRole="button"
72
+ accessibilityState={{ selected: choisi }}
73
+ disabled={choisi}
74
+ onPress={() => configBloc.add(new ConfigChangeEvent(ConfigKey.preferedTheme, item))}
75
+ style={[styles.item, { width: largeur }]}
76
+ >
77
+ {/* Aperçu, entouré quand il est choisi */}
78
+ <View style={[styles.cadre, { borderColor: choisi ? theme.colorScheme.primary : 'transparent' }]}>
79
+ <Image
80
+ source={themeIcon(item)}
81
+ style={[styles.apercu, { backgroundColor: theme.colorScheme.surface }]}
82
+ contentFit="cover"
83
+ />
84
+ </View>
85
+ {choisi && (
86
+ <View style={[styles.coche, { backgroundColor: theme.colorScheme.primary, borderColor: theme.colorScheme.surface }]}>
87
+ <MaterialIcons name="check" size={14} color={theme.colorScheme.onPrimary} />
88
+ </View>
89
+ )}
90
+ <Text
91
+ style={[
92
+ theme.textTheme.displayMedium,
93
+ styles.libelle,
94
+ { fontWeight: choisi ? '700' : '500' },
95
+ choisi ? null : { color: Themer.neural03Color },
96
+ ]}
97
+ >
98
+ {themeTitle(traductions, item)}
99
+ </Text>
100
+ </Pressable>
101
+ );
102
+ })}
103
+ </View>
104
+ </Card>
105
+
106
+ {/* Couleur d'accentuation */}
107
+ <View style={styles.espace} />
108
+ <ProfileSectionTitre titre={traductions.appSettingPageAccentSection} />
109
+ <Card style={styles.card}>
110
+ <View style={styles.pastilles}>
111
+ {[
112
+ // Couleur du thème personnalisé de l'hôte, en tête
113
+ ...(Themer.hasCustomTheme ? [Themer.customAccentCode] : []),
114
+ ...Accents.list(),
115
+ ].map((accent) => {
116
+ const apercu = Themer.get(sombre ? Themes.dark : Themes.light, accent).colorScheme;
117
+ const teintes = { accent: apercu.primary, surAccent: apercu.onPrimary };
118
+ const choisi = currentAccent === accent;
119
+ return (
120
+ <Pressable
121
+ key={accent}
122
+ accessibilityRole="button"
123
+ accessibilityLabel={Themer.accentLabel(traductions, accent)}
124
+ accessibilityState={{ selected: choisi }}
125
+ disabled={choisi}
126
+ onPress={() => configBloc.add(new ConfigChangeEvent(ConfigKey.preferedAccent, accent))}
127
+ style={styles.accent}
128
+ >
129
+ {/* Pastille, cochée quand elle est choisie */}
130
+ <View style={[styles.anneau, { borderColor: choisi ? theme.colorScheme.onSurface : 'transparent' }]}>
131
+ <View style={[styles.pastille, { backgroundColor: teintes.accent }]}>
132
+ {choisi && <MaterialIcons name="check" size={22} color={teintes.surAccent} />}
133
+ </View>
134
+ </View>
135
+ <Text
136
+ numberOfLines={1}
137
+ style={[
138
+ theme.textTheme.displaySmall,
139
+ styles.libelle,
140
+ { fontWeight: choisi ? '700' : '400' },
141
+ { color: choisi ? theme.colorScheme.onSurface : Themer.neural03Color },
142
+ ]}
143
+ >
144
+ {Themer.accentLabel(traductions, accent)}
145
+ </Text>
146
+ </Pressable>
147
+ );
148
+ })}
97
149
  </View>
98
150
  </Card>
99
151
  </ScrollView>
@@ -102,113 +154,18 @@ export function ProfileParametrePageTheme() {
102
154
  );
103
155
  }
104
156
 
105
- /** Une option de thème : aperçu, entouré et coché quand il est choisi, puis son nom */
106
- function ThemeOption({
107
- largeur,
108
- title,
109
- selected,
110
- onSelect,
111
- preview,
112
- }: {
113
- largeur: number;
114
- title: string;
115
- selected: boolean;
116
- onSelect: () => void;
117
- preview: ReactNode;
118
- }) {
119
- const theme = useTheme();
120
- return (
121
- <Pressable
122
- accessibilityRole="button"
123
- accessibilityState={{ selected }}
124
- disabled={selected}
125
- onPress={onSelect}
126
- style={[styles.item, { width: largeur }]}
127
- >
128
- {/* Aperçu, entouré quand il est choisi */}
129
- <View style={[styles.cadre, { borderColor: selected ? theme.colorScheme.primary : 'transparent' }]}>
130
- <View style={[styles.apercu, { backgroundColor: theme.colorScheme.surface }]}>{preview}</View>
131
- </View>
132
- {selected && (
133
- <View style={[styles.coche, { backgroundColor: theme.colorScheme.primary, borderColor: theme.colorScheme.surface }]}>
134
- <MaterialIcons name="check" size={14} color={theme.colorScheme.onPrimary} />
135
- </View>
136
- )}
137
- <Text
138
- style={[
139
- theme.textTheme.displayMedium,
140
- styles.libelle,
141
- { fontWeight: selected ? '700' : '500' },
142
- selected ? null : { color: Themer.neural03Color },
143
- ]}
144
- >
145
- {title}
146
- </Text>
147
- </Pressable>
148
- );
149
- }
150
-
151
- /**
152
- * Aperçu du thème personnalisé de l'hôte (pas d'image comme pour les
153
- * thèmes du SDK) : une miniature d'accueil dessinée avec ses couleurs.
154
- */
155
- function CustomThemePreview({ theme }: { theme: ThemeData }) {
156
- const primary = theme.colorScheme.primary;
157
- const secondary = theme.colorScheme.secondary;
158
- const bar = (width: number, color: string) => <View style={[styles.bar, { width, backgroundColor: color }]} />;
159
- return (
160
- <View style={[styles.flex, styles.mock, { backgroundColor: theme.scaffoldBackgroundColor }]}>
161
- {/* Avatar et titre */}
162
- <View style={styles.mockRow}>
163
- <View style={[styles.dot16, { backgroundColor: secondary }]} />
164
- {bar(40, Themer.neural02Color)}
165
- </View>
166
- <View style={styles.gap12} />
167
- {/* Carte du compte */}
168
- <View style={[styles.mockCard, { backgroundColor: theme.cardColor }]}>
169
- {bar(50, Themer.neural02Color)}
170
- {bar(30, primary)}
171
- </View>
172
- <View style={styles.gap12} />
173
- {/* Boutons d'action */}
174
- <View style={styles.mockActions}>
175
- {[0, 1, 2].map((i) => (
176
- <View key={i} style={[styles.mockButton, { backgroundColor: primary }]} />
177
- ))}
178
- </View>
179
- <View style={styles.gap14} />
180
- {/* Liste */}
181
- {[0, 1, 2].map((i) => (
182
- <View key={i} style={[styles.mockRow, styles.mockListRow]}>
183
- <View style={[styles.dot14, { backgroundColor: secondary }]} />
184
- {bar(46, Themer.neural02Color)}
185
- </View>
186
- ))}
187
- </View>
188
- );
189
- }
190
-
191
- const ESPACE = 12;
192
-
193
157
  const styles = StyleSheet.create({
194
- flex: { flex: 1 },
195
- mock: { padding: 10 },
196
- mockRow: { flexDirection: 'row', alignItems: 'center', gap: 6 },
197
- mockListRow: { marginBottom: 8 },
198
- mockCard: { height: 54, padding: 8, borderRadius: 8, justifyContent: 'space-between' },
199
- mockActions: { flexDirection: 'row', justifyContent: 'space-between' },
200
- mockButton: { width: 26, height: 26, borderRadius: 8 },
201
- bar: { height: 6, borderRadius: 3 },
202
- dot16: { width: 16, height: 16, borderRadius: 8 },
203
- dot14: { width: 14, height: 14, borderRadius: 7 },
204
- gap12: { height: 12 },
205
- gap14: { height: 14 },
206
158
  contenu: { paddingTop: 8, paddingBottom: 16 },
207
159
  card: { margin: 0 },
208
- grille: { padding: 14, flexDirection: 'row', flexWrap: 'wrap', columnGap: ESPACE, rowGap: 16 },
160
+ grille: { padding: 14, flexDirection: 'row', flexWrap: 'wrap', columnGap: 12, rowGap: 16 },
209
161
  item: { alignItems: 'center' },
210
162
  cadre: { alignSelf: 'stretch', padding: 3, borderWidth: 2, borderRadius: 14 },
211
- apercu: { width: '100%', aspectRatio: 0.6, borderRadius: 10, overflow: 'hidden' },
163
+ apercu: { width: '100%', aspectRatio: 0.6, borderRadius: 10 },
212
164
  coche: { position: 'absolute', top: 8, right: 8, borderRadius: 12, borderWidth: 2, padding: 1 },
213
165
  libelle: { marginTop: 8, textAlign: 'center' },
166
+ espace: { height: 24 },
167
+ pastilles: { paddingHorizontal: 10, paddingVertical: 16, flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-evenly', rowGap: 16 },
168
+ accent: { width: 64, alignItems: 'center' },
169
+ anneau: { padding: 3, borderWidth: 2, borderRadius: 26 },
170
+ pastille: { width: 40, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center' },
214
171
  });