@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.
- package/README.md +3 -0
- package/lib/module/core/api.js +9 -0
- package/lib/module/core/api.js.map +1 -1
- package/lib/module/core/pi_spi_sdk_config.js +10 -0
- package/lib/module/core/pi_spi_sdk_config.js.map +1 -1
- 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/core/api.d.ts.map +1 -1
- package/lib/typescript/src/core/pi_spi_sdk_config.d.ts +10 -0
- package/lib/typescript/src/core/pi_spi_sdk_config.d.ts.map +1 -1
- 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/core/api.ts +4 -0
- package/src/core/pi_spi_sdk_config.ts +14 -0
- 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,6 +1,6 @@
|
|
|
1
1
|
import { MaterialIcons } from '@expo/vector-icons';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { useState, type ComponentProps } from 'react';
|
|
3
|
+
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
|
|
6
6
|
import { Images } from '../../../../../core/assets';
|
|
@@ -10,11 +10,12 @@ import { SoundManager } from '../../../../../core/sounds';
|
|
|
10
10
|
import { useTheme } from '../../../../../core/theme';
|
|
11
11
|
import { useAppLocalizations } from '../../../../../l10n/localizations';
|
|
12
12
|
import { TextButton } from '../../../../../shared/material/buttons';
|
|
13
|
-
import {
|
|
13
|
+
import { AppBar } from '../../../../../shared/material/scaffold';
|
|
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 { ConfigKey } from '../../../../config/domain/models/config_keys';
|
|
17
|
-
import {
|
|
17
|
+
import { ProfileMenuTile, ProfilePageMenu } from '../profile_menu';
|
|
18
|
+
import { SwitchThemed } from './settings_widgets';
|
|
18
19
|
|
|
19
20
|
const NONE = 'NONE';
|
|
20
21
|
|
|
@@ -44,77 +45,106 @@ export function ProfileParametrePageNotificationsSon() {
|
|
|
44
45
|
}
|
|
45
46
|
};
|
|
46
47
|
|
|
48
|
+
const basculer = () => setSelectedSound(enabled ? NONE : (SoundManager.liste[0]?.path ?? null));
|
|
49
|
+
|
|
47
50
|
return (
|
|
48
51
|
<View style={[styles.flex, { paddingBottom: insets.bottom }]}>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
{/* Titre dans la barre, comme les autres pages (dans la feuille : fond de la feuille) */}
|
|
53
|
+
<AppBar
|
|
54
|
+
backgroundColor="transparent"
|
|
55
|
+
onBack={() => AppRouter.pop(router)}
|
|
56
|
+
title={
|
|
57
|
+
<Text numberOfLines={1} style={theme.textTheme.headlineMedium}>
|
|
58
|
+
{traductions.appSettingPageMenuNotificationSonPageTitle}
|
|
59
|
+
</Text>
|
|
60
|
+
}
|
|
61
|
+
actions={
|
|
62
|
+
<View style={styles.save}>
|
|
63
|
+
<TextButton onPress={save}>{traductions.btnTextSave}</TextButton>
|
|
64
|
+
</View>
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
57
67
|
<ScrollView style={styles.flex} contentContainerStyle={styles.content}>
|
|
58
|
-
<Text style={theme.textTheme.headlineMedium}>{traductions.appSettingPageMenuNotificationSonPageTitle}</Text>
|
|
59
|
-
<View style={styles.gap20} />
|
|
60
68
|
{/* Son activé ou non */}
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
<ProfilePageMenu
|
|
70
|
+
items={[
|
|
71
|
+
<ProfileMenuTile
|
|
72
|
+
key="son"
|
|
73
|
+
image={Images.iconsCloche}
|
|
74
|
+
titre={traductions.notificationPageTitle}
|
|
75
|
+
onPress={basculer}
|
|
76
|
+
trailing={<SwitchThemed value={enabled} onChange={basculer} />}
|
|
77
|
+
/>,
|
|
78
|
+
]}
|
|
66
79
|
/>
|
|
80
|
+
|
|
67
81
|
{/* Sons disponibles */}
|
|
68
|
-
<View style={styles.
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<SoundRow
|
|
74
|
-
label={traductions.appSettingPageMenuNotificationStyleNone}
|
|
75
|
-
checked={selectedSound === NONE}
|
|
76
|
-
onPress={() => setSelectedSound(NONE)}
|
|
77
|
-
/>
|
|
78
|
-
{SoundManager.liste.map((sound) => (
|
|
82
|
+
<View style={styles.gap24} />
|
|
83
|
+
<ProfilePageMenu
|
|
84
|
+
titre={traductions.appSettingPageMenuNotificationSonTitle}
|
|
85
|
+
items={[
|
|
86
|
+
// Aucun
|
|
79
87
|
<SoundRow
|
|
80
|
-
key={
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
key={NONE}
|
|
89
|
+
icone="music-off"
|
|
90
|
+
label={traductions.appSettingPageMenuNotificationStyleNone}
|
|
91
|
+
checked={selectedSound === NONE}
|
|
92
|
+
onPress={() => setSelectedSound(NONE)}
|
|
93
|
+
/>,
|
|
94
|
+
...SoundManager.liste.map((sound) => (
|
|
95
|
+
<SoundRow
|
|
96
|
+
key={sound.path}
|
|
97
|
+
icone="music-note"
|
|
98
|
+
label={sound.name}
|
|
99
|
+
checked={selectedSound === sound.path}
|
|
100
|
+
onPress={() => {
|
|
101
|
+
void SoundManager.playSound(sound.path);
|
|
102
|
+
setSelectedSound(sound.path);
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
)),
|
|
106
|
+
]}
|
|
107
|
+
/>
|
|
90
108
|
<View style={styles.gap20} />
|
|
91
109
|
</ScrollView>
|
|
92
110
|
</View>
|
|
93
111
|
);
|
|
94
112
|
}
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
/** Son de la liste, coché quand il est choisi */
|
|
115
|
+
function SoundRow({
|
|
116
|
+
icone,
|
|
117
|
+
label,
|
|
118
|
+
checked,
|
|
119
|
+
onPress,
|
|
120
|
+
}: {
|
|
121
|
+
icone: ComponentProps<typeof MaterialIcons>['name'];
|
|
122
|
+
label: string;
|
|
123
|
+
checked: boolean;
|
|
124
|
+
onPress: () => void;
|
|
125
|
+
}) {
|
|
97
126
|
const theme = useTheme();
|
|
98
127
|
return (
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
<ProfileMenuTile
|
|
129
|
+
icone={icone}
|
|
130
|
+
titre={label}
|
|
102
131
|
onPress={onPress}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
trailing={
|
|
133
|
+
checked ? (
|
|
134
|
+
<MaterialIcons name="check-circle" size={22} color={theme.colorScheme.primary} />
|
|
135
|
+
) : (
|
|
136
|
+
<View style={styles.vide} />
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
/>
|
|
108
140
|
);
|
|
109
141
|
}
|
|
110
142
|
|
|
111
143
|
const styles = StyleSheet.create({
|
|
112
144
|
flex: { flex: 1 },
|
|
113
|
-
bar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
114
|
-
back: { padding: 12 },
|
|
115
145
|
save: { paddingRight: 12 },
|
|
116
|
-
content: { paddingTop:
|
|
117
|
-
|
|
118
|
-
gap10: { height: 10 },
|
|
146
|
+
content: { paddingTop: 8, paddingHorizontal: 20 },
|
|
147
|
+
vide: { width: 22 },
|
|
119
148
|
gap20: { height: 20 },
|
|
149
|
+
gap24: { height: 24 },
|
|
120
150
|
});
|
|
@@ -1,52 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
1
|
+
import { StyleSheet, Text, View } from 'react-native';
|
|
3
2
|
|
|
4
3
|
import { Images } from '../../../../../core/assets';
|
|
5
4
|
import { useBloc, useBlocBuilder } from '../../../../../core/bloc_react';
|
|
6
|
-
import { useTheme } from '../../../../../core/theme';
|
|
5
|
+
import { Themer, useTheme } from '../../../../../core/theme';
|
|
7
6
|
import { useAppLocalizations } from '../../../../../l10n/localizations';
|
|
7
|
+
import { SbSelectionSheet } 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';
|
|
11
11
|
import { ConfigKey } from '../../../../config/domain/models/config_keys';
|
|
12
|
-
import {
|
|
12
|
+
import { ProfileMenuTile, ProfilePageMenu } from '../profile_menu';
|
|
13
|
+
import { SwitchThemed } from './settings_widgets';
|
|
13
14
|
|
|
14
15
|
/** Feuille « Mon QR code » : afficher ou non le QR code de l'alias sur l'accueil. */
|
|
15
16
|
export function ProfileParametrePageQRcode() {
|
|
16
17
|
const traductions = useAppLocalizations();
|
|
17
18
|
const theme = useTheme();
|
|
18
|
-
const insets = useSafeAreaInsets();
|
|
19
|
-
const { height } = useWindowDimensions();
|
|
20
19
|
const configBloc = useBloc(ConfigBloc);
|
|
21
20
|
const state = useBlocBuilder(
|
|
22
21
|
configBloc,
|
|
23
22
|
(_p, current) => current instanceof ConfigLoadedState && current.updatedKey === ConfigKey.showQRcode,
|
|
24
23
|
);
|
|
25
24
|
const showQRcode = state.configParams.params[ConfigKey.showQRcode] !== '0';
|
|
25
|
+
const basculer = () => configBloc.add(new ConfigChangeEvent(ConfigKey.showQRcode, showQRcode ? '0' : '1'));
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
<Text style={theme.textTheme.displaySmall}>
|
|
28
|
+
<SbSelectionSheet
|
|
29
|
+
titre={traductions.appSettingPageMenuQrCodeTitle}
|
|
30
|
+
entete={
|
|
31
|
+
<Text style={[theme.textTheme.displaySmall, { color: Themer.neural03Color }]}>
|
|
33
32
|
{showQRcode ? traductions.appSettingPageMenuQrCodeDeselectTitle : traductions.appSettingPageMenuQrCodeSelectTitle}
|
|
34
33
|
</Text>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
}
|
|
35
|
+
contenu={
|
|
36
|
+
<View style={styles.contenu}>
|
|
37
|
+
<ProfilePageMenu
|
|
38
|
+
items={[
|
|
39
|
+
<ProfileMenuTile
|
|
40
|
+
key="qrcode"
|
|
41
|
+
image={Images.homeFooterQrcode}
|
|
42
|
+
titre={traductions.appSettingPageMenuQrCodeTitle}
|
|
43
|
+
onPress={basculer}
|
|
44
|
+
trailing={<SwitchThemed value={showQRcode} onChange={basculer} />}
|
|
45
|
+
/>,
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
</View>
|
|
49
|
+
}
|
|
50
|
+
/>
|
|
44
51
|
);
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
const styles = StyleSheet.create({
|
|
48
|
-
|
|
49
|
-
content: { padding: 20 },
|
|
50
|
-
gap8: { height: 8 },
|
|
51
|
-
gap15: { height: 15 },
|
|
55
|
+
contenu: { paddingHorizontal: 20, paddingTop: 8, paddingBottom: 20 },
|
|
52
56
|
});
|
|
@@ -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
|
});
|