@pi-spi/app 1.5.1 → 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.
- package/lib/module/l10n/app_localizations.js.map +1 -1
- package/lib/module/l10n/app_localizations_en.js +12 -1
- package/lib/module/l10n/app_localizations_en.js.map +1 -1
- package/lib/module/l10n/app_localizations_fr.js +12 -1
- package/lib/module/l10n/app_localizations_fr.js.map +1 -1
- package/lib/module/l10n/app_localizations_pt.js +12 -1
- package/lib/module/l10n/app_localizations_pt.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_delete_btn_widget.js +15 -19
- package/lib/module/modules/alias/presentation/pages/alias_delete_btn_widget.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_page_claim.js +4 -11
- package/lib/module/modules/alias/presentation/pages/alias_page_claim.js.map +1 -1
- package/lib/module/modules/compte/presentation/pages/compte_details_widget.js +6 -7
- package/lib/module/modules/compte/presentation/pages/compte_details_widget.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/compte/profile_compte_page.js +25 -11
- package/lib/module/modules/profile/presentation/pages/compte/profile_compte_page.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page.js +12 -18
- 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 +109 -103
- 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_notifications_son.js +55 -72
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications_son.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.js +31 -45
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js +103 -67
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/profile_app_bar.js +26 -0
- package/lib/module/modules/profile/presentation/pages/profile_app_bar.js.map +1 -0
- package/lib/module/modules/profile/presentation/pages/profile_menu.js +143 -34
- package/lib/module/modules/profile/presentation/pages/profile_menu.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/profile_page.js +95 -63
- package/lib/module/modules/profile/presentation/pages/profile_page.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/profile_page_avatar.js +97 -45
- package/lib/module/modules/profile/presentation/pages/profile_page_avatar.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/profile_page_contacts.js +9 -8
- package/lib/module/modules/profile/presentation/pages/profile_page_contacts.js.map +1 -1
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js +26 -2
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js.map +1 -1
- package/lib/module/modules/search/presentation/pages/search_page.js +0 -1
- package/lib/module/modules/search/presentation/pages/search_page.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.js +4 -11
- package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.js +13 -10
- package/lib/module/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.js.map +1 -1
- package/lib/module/shared/utils/color.js +15 -0
- package/lib/module/shared/utils/color.js.map +1 -0
- package/lib/module/shared/widgets/error_card_widget.js +70 -0
- package/lib/module/shared/widgets/error_card_widget.js.map +1 -0
- package/lib/module/shared/widgets/selection.js +136 -0
- package/lib/module/shared/widgets/selection.js.map +1 -0
- package/lib/typescript/src/l10n/app_localizations.d.ts +22 -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/alias/presentation/pages/alias_delete_btn_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/alias/presentation/pages/alias_page_claim.d.ts.map +1 -1
- package/lib/typescript/src/modules/compte/presentation/pages/compte_details_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/compte/profile_compte_page.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_notifications.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications_son.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.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/profile_app_bar.d.ts +5 -0
- package/lib/typescript/src/modules/profile/presentation/pages/profile_app_bar.d.ts.map +1 -0
- package/lib/typescript/src/modules/profile/presentation/pages/profile_menu.d.ts +31 -4
- package/lib/typescript/src/modules/profile/presentation/pages/profile_menu.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/profile_page.d.ts +4 -1
- package/lib/typescript/src/modules/profile/presentation/pages/profile_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/profile_page_avatar.d.ts +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/profile_page_avatar.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/profile_page_contacts.d.ts.map +1 -1
- package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.d.ts.map +1 -1
- package/lib/typescript/src/shared/utils/color.d.ts +7 -0
- package/lib/typescript/src/shared/utils/color.d.ts.map +1 -0
- package/lib/typescript/src/shared/widgets/error_card_widget.d.ts +11 -0
- package/lib/typescript/src/shared/widgets/error_card_widget.d.ts.map +1 -0
- package/lib/typescript/src/shared/widgets/selection.d.ts +21 -0
- package/lib/typescript/src/shared/widgets/selection.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/l10n/app_localizations.ts +22 -0
- package/src/l10n/app_localizations_en.ts +11 -0
- package/src/l10n/app_localizations_fr.ts +11 -0
- package/src/l10n/app_localizations_pt.ts +11 -0
- package/src/modules/alias/presentation/pages/alias_delete_btn_widget.tsx +13 -12
- package/src/modules/alias/presentation/pages/alias_page_claim.tsx +5 -4
- package/src/modules/compte/presentation/pages/compte_details_widget.tsx +3 -4
- package/src/modules/profile/presentation/pages/compte/profile_compte_page.tsx +15 -7
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page.tsx +10 -12
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx +89 -68
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications_son.tsx +84 -54
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_qrcode.tsx +28 -24
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_theme.tsx +77 -44
- package/src/modules/profile/presentation/pages/profile_app_bar.tsx +21 -0
- package/src/modules/profile/presentation/pages/profile_menu.tsx +126 -33
- package/src/modules/profile/presentation/pages/profile_page.tsx +90 -66
- package/src/modules/profile/presentation/pages/profile_page_avatar.tsx +69 -28
- package/src/modules/profile/presentation/pages/profile_page_contacts.tsx +5 -4
- package/src/modules/search/presentation/pages/search_fonctionnalites.ts +40 -7
- package/src/modules/search/presentation/pages/search_page.tsx +1 -1
- package/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page.tsx +2 -4
- package/src/modules/transactions/presentation/pages/transaction_rtp/transaction_rtp_page_actions.tsx +13 -6
- package/src/shared/utils/color.ts +14 -0
- package/src/shared/widgets/error_card_widget.tsx +58 -0
- package/src/shared/widgets/selection.tsx +98 -0
|
@@ -1,21 +1,22 @@
|
|
|
1
|
+
import { MaterialIcons } from '@expo/vector-icons';
|
|
1
2
|
import { Image } from 'expo-image';
|
|
2
|
-
import type
|
|
3
|
+
import { useState, type ReactNode } from 'react';
|
|
3
4
|
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
4
5
|
|
|
5
6
|
import { Images } from '../../../../../core/assets';
|
|
6
7
|
import { useBloc, useBlocBuilder } from '../../../../../core/bloc_react';
|
|
7
|
-
import { AppRouter, useAppRouter } from '../../../../../core/router';
|
|
8
8
|
import { Themer, Themes, useTheme, type ThemeData } 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';
|
|
12
|
-
import {
|
|
12
|
+
import { Scaffold } from '../../../../../shared/material/scaffold';
|
|
13
13
|
import { MyPageContainer } from '../../../../../shared/widgets/my_page_container';
|
|
14
14
|
import { ConfigBloc } from '../../../../config/adapters/ui/bloc/config_bloc';
|
|
15
15
|
import { ConfigChangeEvent } from '../../../../config/adapters/ui/bloc/config_event';
|
|
16
16
|
import { ConfigLoadedState } from '../../../../config/adapters/ui/bloc/config_state';
|
|
17
17
|
import { ConfigKey } from '../../../../config/domain/models/config_keys';
|
|
18
|
-
import {
|
|
18
|
+
import { ProfileAppBar } from '../profile_app_bar';
|
|
19
|
+
import { ProfileSectionTitre } from '../profile_menu';
|
|
19
20
|
|
|
20
21
|
function themeIcon(theme: Themes): number {
|
|
21
22
|
switch (theme) {
|
|
@@ -49,8 +50,6 @@ function themeTitle(traductions: AppLocalizations, theme: Themes): string {
|
|
|
49
50
|
/** Choix du thème de l'application (route `profileParametreTheme`). */
|
|
50
51
|
export function ProfileParametrePageTheme() {
|
|
51
52
|
const traductions = useAppLocalizations();
|
|
52
|
-
const theme = useTheme();
|
|
53
|
-
const router = useAppRouter();
|
|
54
53
|
const configBloc = useBloc(ConfigBloc);
|
|
55
54
|
const state = useBlocBuilder(
|
|
56
55
|
configBloc,
|
|
@@ -58,34 +57,43 @@ export function ProfileParametrePageTheme() {
|
|
|
58
57
|
);
|
|
59
58
|
const currentTheme = state.configParams.params[ConfigKey.preferedTheme];
|
|
60
59
|
|
|
60
|
+
const [largeurGrille, setLargeurGrille] = useState(0);
|
|
61
|
+
// Trois aperçus par ligne
|
|
62
|
+
// Arrondi vers le bas : sinon la troisième colonne peut passer à la ligne
|
|
63
|
+
const largeur = Math.floor((largeurGrille - 2 * ESPACE) / 3);
|
|
64
|
+
|
|
61
65
|
return (
|
|
62
|
-
<Scaffold appBar={<
|
|
66
|
+
<Scaffold appBar={<ProfileAppBar titre={traductions.appSettingPageMenuThemePageTitle} />}>
|
|
63
67
|
<MyPageContainer>
|
|
64
|
-
<ScrollView>
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
<ScrollView contentContainerStyle={styles.contenu}>
|
|
69
|
+
{/* Thème */}
|
|
70
|
+
<ProfileSectionTitre titre={traductions.appSettingPageMenuThemeTitle} />
|
|
71
|
+
<Card style={styles.card}>
|
|
72
|
+
<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
|
|
87
|
+
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
|
+
</>
|
|
79
96
|
)}
|
|
80
|
-
{Themes.list().map((item) => (
|
|
81
|
-
<ThemeOption
|
|
82
|
-
key={item}
|
|
83
|
-
title={themeTitle(traductions, item)}
|
|
84
|
-
selected={currentTheme === item}
|
|
85
|
-
onSelect={() => configBloc.add(new ConfigChangeEvent(ConfigKey.preferedTheme, item))}
|
|
86
|
-
preview={<Image source={themeIcon(item)} style={styles.flex} contentFit="cover" />}
|
|
87
|
-
/>
|
|
88
|
-
))}
|
|
89
97
|
</View>
|
|
90
98
|
</Card>
|
|
91
99
|
</ScrollView>
|
|
@@ -94,13 +102,15 @@ export function ProfileParametrePageTheme() {
|
|
|
94
102
|
);
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
/** Une option de thème : aperçu,
|
|
105
|
+
/** Une option de thème : aperçu, entouré et coché quand il est choisi, puis son nom */
|
|
98
106
|
function ThemeOption({
|
|
107
|
+
largeur,
|
|
99
108
|
title,
|
|
100
109
|
selected,
|
|
101
110
|
onSelect,
|
|
102
111
|
preview,
|
|
103
112
|
}: {
|
|
113
|
+
largeur: number;
|
|
104
114
|
title: string;
|
|
105
115
|
selected: boolean;
|
|
106
116
|
onSelect: () => void;
|
|
@@ -108,13 +118,32 @@ function ThemeOption({
|
|
|
108
118
|
}) {
|
|
109
119
|
const theme = useTheme();
|
|
110
120
|
return (
|
|
111
|
-
<Pressable
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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>
|
|
118
147
|
</Pressable>
|
|
119
148
|
);
|
|
120
149
|
}
|
|
@@ -159,6 +188,8 @@ function CustomThemePreview({ theme }: { theme: ThemeData }) {
|
|
|
159
188
|
);
|
|
160
189
|
}
|
|
161
190
|
|
|
191
|
+
const ESPACE = 12;
|
|
192
|
+
|
|
162
193
|
const styles = StyleSheet.create({
|
|
163
194
|
flex: { flex: 1 },
|
|
164
195
|
mock: { padding: 10 },
|
|
@@ -172,10 +203,12 @@ const styles = StyleSheet.create({
|
|
|
172
203
|
dot14: { width: 14, height: 14, borderRadius: 7 },
|
|
173
204
|
gap12: { height: 12 },
|
|
174
205
|
gap14: { height: 14 },
|
|
175
|
-
|
|
206
|
+
contenu: { paddingTop: 8, paddingBottom: 16 },
|
|
207
|
+
card: { margin: 0 },
|
|
208
|
+
grille: { padding: 14, flexDirection: 'row', flexWrap: 'wrap', columnGap: ESPACE, rowGap: 16 },
|
|
176
209
|
item: { alignItems: 'center' },
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
210
|
+
cadre: { alignSelf: 'stretch', padding: 3, borderWidth: 2, borderRadius: 14 },
|
|
211
|
+
apercu: { width: '100%', aspectRatio: 0.6, borderRadius: 10, overflow: 'hidden' },
|
|
212
|
+
coche: { position: 'absolute', top: 8, right: 8, borderRadius: 12, borderWidth: 2, padding: 1 },
|
|
213
|
+
libelle: { marginTop: 8, textAlign: 'center' },
|
|
181
214
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Text } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { AppRouter, useAppRouter } from '../../../../core/router';
|
|
4
|
+
import { useTheme } from '../../../../core/theme';
|
|
5
|
+
import { AppBar } from '../../../../shared/material/scaffold';
|
|
6
|
+
|
|
7
|
+
/** Barre des écrans du profil : bouton retour et [titre] de la page. */
|
|
8
|
+
export function ProfileAppBar({ titre }: { titre: string }) {
|
|
9
|
+
const theme = useTheme();
|
|
10
|
+
const router = useAppRouter();
|
|
11
|
+
return (
|
|
12
|
+
<AppBar
|
|
13
|
+
onBack={() => AppRouter.pop(router)}
|
|
14
|
+
title={
|
|
15
|
+
<Text numberOfLines={1} style={[theme.textTheme.headlineMedium, { color: theme.appBarTheme.foregroundColor }]}>
|
|
16
|
+
{titre}
|
|
17
|
+
</Text>
|
|
18
|
+
}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { MaterialIcons } from '@expo/vector-icons';
|
|
1
2
|
import { Image } from 'expo-image';
|
|
2
|
-
import type
|
|
3
|
-
import { Pressable, StyleSheet, Switch, Text, View } from 'react-native';
|
|
3
|
+
import { Fragment, isValidElement, type ComponentProps, type ReactElement, type ReactNode } from 'react';
|
|
4
|
+
import { Platform, Pressable, StyleSheet, Switch, Text, View } from 'react-native';
|
|
4
5
|
|
|
5
6
|
import { AppRouter, useAppRouter } from '../../../../core/router';
|
|
6
|
-
import { useTheme } from '../../../../core/theme';
|
|
7
|
+
import { Themer, useTheme } from '../../../../core/theme';
|
|
7
8
|
import { Card } from '../../../../shared/material/card';
|
|
8
9
|
import { showModalBottomSheet } from '../../../../shared/material/overlays';
|
|
10
|
+
import { withAlpha } from '../../../../shared/utils/color';
|
|
9
11
|
|
|
10
12
|
/** Une entrée du menu du profil : une route, une action, ou une feuille à ouvrir. */
|
|
11
13
|
export class ProfileItem {
|
|
@@ -27,45 +29,136 @@ export class ProfileItem {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Groupe d'entrées du profil dans une carte : [titre] de section facultatif
|
|
34
|
+
* au-dessus, puis une ligne par élément, séparées par un trait. Les [items]
|
|
35
|
+
* sont des `ProfileItem` ou des composants déjà construits (ex. bouton de
|
|
36
|
+
* modification du code PIN, qui utilise `ProfileMenuTile`).
|
|
37
|
+
*/
|
|
38
|
+
export function ProfilePageMenu({ items, titre }: { items: (ProfileItem | ReactElement)[]; titre?: string }) {
|
|
32
39
|
const theme = useTheme();
|
|
33
40
|
const router = useAppRouter();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
const lignes = items
|
|
42
|
+
.map((item) => {
|
|
43
|
+
if (!(item instanceof ProfileItem)) return isValidElement(item) ? item : null;
|
|
44
|
+
const aSwitch = item.action != null && item.value != null;
|
|
45
|
+
return (
|
|
46
|
+
<ProfileMenuTile
|
|
47
|
+
image={item.icon}
|
|
48
|
+
titre={item.title}
|
|
49
|
+
sousTitre={item.subtitle}
|
|
40
50
|
onPress={() => {
|
|
41
51
|
if (typeof item.page === 'string') void AppRouter.push(router, item.page);
|
|
42
52
|
else if (typeof item.page === 'function') item.page();
|
|
43
53
|
// Ouvrir une feuille
|
|
44
|
-
else if (item.sheet != null) void showModalBottomSheet(router, {
|
|
54
|
+
else if (item.sheet != null) void showModalBottomSheet(router, { builder: item.sheet });
|
|
45
55
|
}}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
trailing={
|
|
57
|
+
aSwitch ? (
|
|
58
|
+
<Switch
|
|
59
|
+
value={item.value}
|
|
60
|
+
onValueChange={item.action}
|
|
61
|
+
trackColor={{ false: theme.switchTheme.inactiveTrackColor, true: theme.switchTheme.activeTrackColor }}
|
|
62
|
+
thumbColor={theme.switchTheme.thumbColor}
|
|
63
|
+
/>
|
|
64
|
+
) : undefined
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
})
|
|
69
|
+
.filter((ligne): ligne is ReactElement => ligne != null);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<View>
|
|
73
|
+
{titre != null && <ProfileSectionTitre titre={titre} />}
|
|
74
|
+
<Card style={styles.card}>
|
|
75
|
+
{lignes.map((ligne, i) => (
|
|
76
|
+
<Fragment key={i}>
|
|
77
|
+
{i > 0 && <View style={styles.separateur} />}
|
|
78
|
+
{ligne}
|
|
79
|
+
</Fragment>
|
|
80
|
+
))}
|
|
81
|
+
</Card>
|
|
82
|
+
</View>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Ligne d'un menu du profil : pastille avec l'icône, [titre], [sousTitre]
|
|
88
|
+
* facultatif et, à droite, [trailing] (ex. Switch) ou une flèche.
|
|
89
|
+
* [couleur] remplace la couleur principale (ex. rouge pour une action
|
|
90
|
+
* destructive).
|
|
91
|
+
*/
|
|
92
|
+
export function ProfileMenuTile({
|
|
93
|
+
image,
|
|
94
|
+
icone,
|
|
95
|
+
titre,
|
|
96
|
+
sousTitre,
|
|
97
|
+
onPress,
|
|
98
|
+
trailing,
|
|
99
|
+
couleur,
|
|
100
|
+
disabled,
|
|
101
|
+
}: {
|
|
102
|
+
image?: number;
|
|
103
|
+
icone?: ComponentProps<typeof MaterialIcons>['name'];
|
|
104
|
+
titre: string;
|
|
105
|
+
sousTitre?: string;
|
|
106
|
+
onPress?: () => void;
|
|
107
|
+
trailing?: ReactNode;
|
|
108
|
+
couleur?: string;
|
|
109
|
+
disabled?: boolean;
|
|
110
|
+
}) {
|
|
111
|
+
const theme = useTheme();
|
|
112
|
+
const principale = couleur ?? theme.colorScheme.primary;
|
|
113
|
+
return (
|
|
114
|
+
<Pressable
|
|
115
|
+
accessibilityRole="button"
|
|
116
|
+
disabled={disabled}
|
|
117
|
+
onPress={onPress}
|
|
118
|
+
style={({ pressed }) => [styles.tile, pressed ? { backgroundColor: theme.colorScheme.secondary } : null]}
|
|
119
|
+
>
|
|
120
|
+
{/* Pastille */}
|
|
121
|
+
<View style={[styles.pastille, { backgroundColor: withAlpha(principale, 0.14) }]}>
|
|
122
|
+
{image != null ? (
|
|
123
|
+
<Image source={image} style={styles.icon} tintColor={couleur ?? theme.colorScheme.onSurface} />
|
|
124
|
+
) : (
|
|
125
|
+
<MaterialIcons name={icone ?? 'circle'} size={20} color={principale} />
|
|
126
|
+
)}
|
|
127
|
+
</View>
|
|
128
|
+
<View style={styles.texts}>
|
|
129
|
+
<Text style={[theme.textTheme.displayLarge, styles.titre, couleur != null ? { color: couleur } : null]}>{titre}</Text>
|
|
130
|
+
{sousTitre != null && sousTitre.length > 0 && (
|
|
131
|
+
<Text style={[theme.textTheme.displaySmall, styles.sousTitre, { color: Themer.neural03Color }]}>{sousTitre}</Text>
|
|
132
|
+
)}
|
|
133
|
+
</View>
|
|
134
|
+
{trailing ?? (
|
|
135
|
+
<MaterialIcons
|
|
136
|
+
name={Platform.OS === 'ios' ? 'arrow-forward-ios' : 'arrow-forward'}
|
|
137
|
+
size={18}
|
|
138
|
+
color={couleur ?? Themer.neural03Color}
|
|
139
|
+
/>
|
|
140
|
+
)}
|
|
141
|
+
</Pressable>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Titre d'une section du profil, en petites capitales. */
|
|
146
|
+
export function ProfileSectionTitre({ titre }: { titre: string }) {
|
|
147
|
+
const theme = useTheme();
|
|
148
|
+
return (
|
|
149
|
+
<Text style={[theme.textTheme.displaySmall, styles.section, { color: Themer.neural03Color }]}>{titre.toUpperCase()}</Text>
|
|
64
150
|
);
|
|
65
151
|
}
|
|
66
152
|
|
|
67
153
|
const styles = StyleSheet.create({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
154
|
+
card: { margin: 0 },
|
|
155
|
+
tile: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 12 },
|
|
156
|
+
pastille: { width: 40, height: 40, borderRadius: 12, alignItems: 'center', justifyContent: 'center' },
|
|
157
|
+
icon: { width: 20, height: 20 },
|
|
158
|
+
texts: { flex: 1, marginLeft: 14, marginRight: 8 },
|
|
159
|
+
titre: { fontWeight: '500' },
|
|
160
|
+
sousTitre: { marginTop: 2 },
|
|
161
|
+
section: { fontSize: 12, letterSpacing: 0.8, fontWeight: '600', marginLeft: 4, marginBottom: 8 },
|
|
162
|
+
// Trait aligné sur le texte (après la pastille)
|
|
163
|
+
separateur: { height: StyleSheet.hairlineWidth, marginLeft: 68, backgroundColor: withAlpha(Themer.neural03Color, 0.25) },
|
|
71
164
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Linking, Platform, StyleSheet, Text, View } from 'react-native';
|
|
1
|
+
import { Linking, Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
2
2
|
|
|
3
3
|
import { Images } from '../../../../core/assets';
|
|
4
4
|
import { AppEnv } from '../../../../core/env';
|
|
@@ -7,9 +7,10 @@ import { AppRouter, useAppRouter } from '../../../../core/router';
|
|
|
7
7
|
import { Themer, useTheme } from '../../../../core/theme';
|
|
8
8
|
import type { AppLocalizations } from '../../../../l10n/app_localizations';
|
|
9
9
|
import { useAppLocalizations } from '../../../../l10n/localizations';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { Scaffold } from '../../../../shared/material/scaffold';
|
|
11
|
+
import { withAlpha } from '../../../../shared/utils/color';
|
|
12
12
|
import { MyPageContainer } from '../../../../shared/widgets/my_page_container';
|
|
13
|
+
import { ProfileAppBar } from './profile_app_bar';
|
|
13
14
|
import { ProfilePageMenu, ProfileItem } from './profile_menu';
|
|
14
15
|
import { ProfilePageAvatar } from './profile_page_avatar';
|
|
15
16
|
|
|
@@ -23,7 +24,7 @@ async function launchUrl(url: string): Promise<void> {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
function
|
|
27
|
+
function menuCompteItems(traductions: AppLocalizations): ProfileItem[] {
|
|
27
28
|
const items = [
|
|
28
29
|
// Compte
|
|
29
30
|
new ProfileItem(Images.iconsCircleUser, traductions.profilePageMenuCompteTitle, AppRouter.profileCompte),
|
|
@@ -34,84 +35,107 @@ function menuItems(traductions: AppLocalizations): ProfileItem[] {
|
|
|
34
35
|
if (Platform.OS !== 'web') {
|
|
35
36
|
items.push(new ProfileItem(Images.iconTransactionSendByAlias, traductions.profilePageMenuAliasContactTitle, AppRouter.profileContacts));
|
|
36
37
|
}
|
|
37
|
-
// Centre d'aide
|
|
38
|
-
if (AppEnv.aideLink != null && AppEnv.aideLink.length > 0) {
|
|
39
|
-
const aideLink = AppEnv.aideLink;
|
|
40
|
-
items.push(
|
|
41
|
-
new ProfileItem(
|
|
42
|
-
Images.iconsHelp,
|
|
43
|
-
traductions.profilePageMenuHelpTitle,
|
|
44
|
-
Platform.OS !== 'web' ? AppRouter.profileHelpCenter : () => void launchUrl(aideLink),
|
|
45
|
-
),
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
38
|
return items;
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
|
|
41
|
+
function menuAssistanceItems(traductions: AppLocalizations): ProfileItem[] {
|
|
42
|
+
// Centre d'aide (seulement si l'hôte fournit un lien)
|
|
43
|
+
const aideLink = AppEnv.aideLink;
|
|
44
|
+
if (aideLink == null || aideLink.length === 0) return [];
|
|
45
|
+
return [
|
|
46
|
+
new ProfileItem(
|
|
47
|
+
Images.iconsHelp,
|
|
48
|
+
traductions.profilePageMenuHelpTitle,
|
|
49
|
+
Platform.OS !== 'web' ? AppRouter.profileHelpCenter : () => void launchUrl(aideLink),
|
|
50
|
+
),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Profil du client (route `profile`) : en-tête de l'utilisateur, menus
|
|
56
|
+
* « Mon compte » et « Assistance », puis liens légaux et version de l'app.
|
|
57
|
+
*/
|
|
52
58
|
export function ProfilePage() {
|
|
53
59
|
const traductions = useAppLocalizations();
|
|
54
60
|
const theme = useTheme();
|
|
55
61
|
const router = useAppRouter();
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
62
|
+
const assistance = menuAssistanceItems(traductions);
|
|
63
|
+
// CGU et politique de confidentialité optionnelles : l'hôte peut ne pas en fournir
|
|
64
|
+
const cguLink = AppEnv.cguLink;
|
|
65
|
+
const pocLink = AppEnv.pocLink;
|
|
66
|
+
const hasCgu = cguLink != null && cguLink.length > 0;
|
|
67
|
+
const hasPoc = pocLink != null && pocLink.length > 0;
|
|
68
|
+
const lienStyle = [
|
|
69
|
+
theme.textTheme.displaySmall,
|
|
70
|
+
{ color: Themer.neural03Color, textDecorationLine: 'underline' as const, textAlign: 'center' as const },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const lien = (texte: string, onPress: () => void) => (
|
|
74
|
+
<Pressable accessibilityRole="link" onPress={onPress} style={styles.lien}>
|
|
75
|
+
<Text style={lienStyle}>{texte}</Text>
|
|
76
|
+
</Pressable>
|
|
77
|
+
);
|
|
59
78
|
|
|
60
79
|
return (
|
|
61
|
-
<Scaffold appBar={<
|
|
80
|
+
<Scaffold appBar={<ProfileAppBar titre={traductions.profilePageTitle} />}>
|
|
62
81
|
<MyPageContainer>
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
</TextButton>
|
|
82
|
+
<ScrollView contentContainerStyle={styles.contenu} showsVerticalScrollIndicator={false}>
|
|
83
|
+
{/* Utilisateur connecté */}
|
|
84
|
+
<ProfilePageAvatar />
|
|
85
|
+
<View style={styles.gap28} />
|
|
86
|
+
|
|
87
|
+
{/* Compte, paramètres, contacts */}
|
|
88
|
+
<ProfilePageMenu titre={traductions.profilePageSectionCompte} items={menuCompteItems(traductions)} />
|
|
89
|
+
<View style={styles.gap24} />
|
|
90
|
+
|
|
91
|
+
{/* Aide (seulement si l'hôte fournit un lien) */}
|
|
92
|
+
{assistance.length > 0 && (
|
|
93
|
+
<>
|
|
94
|
+
<ProfilePageMenu titre={traductions.profilePageSectionAssistance} items={assistance} />
|
|
95
|
+
<View style={styles.gap24} />
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{/* CGU et politique de confidentialité, côte à côte ou l'un sous l'autre */}
|
|
100
|
+
{(hasCgu || hasPoc) && (
|
|
101
|
+
<>
|
|
102
|
+
<View style={styles.liens}>
|
|
103
|
+
{hasCgu &&
|
|
104
|
+
lien(traductions.profilePageCGU, () =>
|
|
105
|
+
Platform.OS !== 'web' ? void AppRouter.push(router, AppRouter.loginCgu) : void launchUrl(cguLink),
|
|
106
|
+
)}
|
|
107
|
+
{hasPoc &&
|
|
108
|
+
lien(traductions.profilePagePC, () =>
|
|
109
|
+
Platform.OS !== 'web' ? void AppRouter.push(router, AppRouter.loginPoc) : void launchUrl(pocLink),
|
|
110
|
+
)}
|
|
93
111
|
</View>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
<View style={styles.gap10} />
|
|
113
|
+
</>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{/* Version : ouvre la fiche de l'app sur le store */}
|
|
117
|
+
<Pressable
|
|
118
|
+
accessibilityRole="link"
|
|
119
|
+
disabled={Platform.OS === 'web'}
|
|
120
|
+
onPress={() => void launchUrl(Platform.OS === 'ios' ? AppEnv.appstoreLink : AppEnv.playstoreLink)}
|
|
121
|
+
style={[styles.version, { backgroundColor: withAlpha(Themer.neural03Color, 0.12) }]}
|
|
122
|
+
>
|
|
123
|
+
<Text style={[theme.textTheme.labelLarge, { color: Themer.neural03Color }]}>
|
|
124
|
+
{`${traductions.profilePageAppVersion} ${AppEnv.version}`}
|
|
125
|
+
</Text>
|
|
126
|
+
</Pressable>
|
|
127
|
+
</ScrollView>
|
|
105
128
|
</MyPageContainer>
|
|
106
129
|
</Scaffold>
|
|
107
130
|
);
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
const styles = StyleSheet.create({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
version: {
|
|
134
|
+
contenu: { paddingTop: 4, paddingBottom: 16 },
|
|
135
|
+
liens: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center', columnGap: 12 },
|
|
136
|
+
lien: { paddingHorizontal: 4, paddingVertical: 6, borderRadius: 6 },
|
|
137
|
+
version: { alignSelf: 'center', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 },
|
|
115
138
|
gap10: { height: 10 },
|
|
116
|
-
|
|
139
|
+
gap24: { height: 24 },
|
|
140
|
+
gap28: { height: 28 },
|
|
117
141
|
});
|