@pi-spi/app 1.3.0 → 1.4.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 +28 -5
- package/assets/icons/qr.png +0 -0
- package/assets/sounds/voice/search_prompt_en.mp3 +0 -0
- package/assets/sounds/voice/search_prompt_fr.mp3 +0 -0
- package/assets/sounds/voice/search_prompt_pt.mp3 +0 -0
- package/example/app.json +9 -1
- package/lib/module/core/assets.js +1 -0
- package/lib/module/core/assets.js.map +1 -1
- package/lib/module/core/router.js +3 -0
- package/lib/module/core/router.js.map +1 -1
- package/lib/module/core/routes.js +14 -0
- package/lib/module/core/routes.js.map +1 -1
- package/lib/module/core/storage.js +27 -29
- package/lib/module/core/storage.js.map +1 -1
- package/lib/module/l10n/app_localizations.js.map +1 -1
- package/lib/module/l10n/app_localizations_en.js +21 -1
- package/lib/module/l10n/app_localizations_en.js.map +1 -1
- package/lib/module/l10n/app_localizations_fr.js +21 -1
- package/lib/module/l10n/app_localizations_fr.js.map +1 -1
- package/lib/module/l10n/app_localizations_pt.js +21 -1
- package/lib/module/l10n/app_localizations_pt.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js +3 -3
- package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_page_otp.js.map +1 -1
- package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js +2 -2
- package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js.map +1 -1
- package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js +12 -10
- package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js.map +1 -1
- package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js +14 -9
- package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js.map +1 -1
- package/lib/module/modules/home/presentation/pages/home_page.js +41 -2
- package/lib/module/modules/home/presentation/pages/home_page.js.map +1 -1
- package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js +10 -5
- package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js.map +1 -1
- package/lib/module/modules/notification/presentation/bloc/notification_bloc.js +11 -4
- package/lib/module/modules/notification/presentation/bloc/notification_bloc.js.map +1 -1
- package/lib/module/modules/notification/presentation/bloc/notification_state.js +7 -1
- package/lib/module/modules/notification/presentation/bloc/notification_state.js.map +1 -1
- package/lib/module/modules/notification/presentation/pages/notification_page_liste.js +6 -2
- package/lib/module/modules/notification/presentation/pages/notification_page_liste.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js +2 -2
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/web/profile_web_page.js.map +1 -1
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page.js.map +1 -1
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js +2 -2
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js.map +1 -1
- package/lib/module/modules/search/domain/models/fonctionnalite.js +199 -0
- package/lib/module/modules/search/domain/models/fonctionnalite.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js +119 -0
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_page.js +359 -0
- package/lib/module/modules/search/presentation/pages/search_page.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_page_voice_button.js +335 -0
- package/lib/module/modules/search/presentation/pages/search_page_voice_button.js.map +1 -0
- package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js +9 -4
- package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js.map +1 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js +13 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js.map +1 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js +7 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js +216 -137
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js +6 -2
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js +3 -3
- package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js.map +1 -1
- package/lib/module/shared/widgets/amount_widget.js +4 -1
- package/lib/module/shared/widgets/amount_widget.js.map +1 -1
- package/lib/module/shared/widgets/input_amount.js.map +1 -1
- package/lib/module/shared/widgets/input_text.js +2 -0
- package/lib/module/shared/widgets/input_text.js.map +1 -1
- package/lib/module/shared/widgets/menu_actions_widget.js +10 -1
- package/lib/module/shared/widgets/menu_actions_widget.js.map +1 -1
- package/lib/module/shared/widgets/pagination_loader_widget.js +29 -0
- package/lib/module/shared/widgets/pagination_loader_widget.js.map +1 -0
- package/lib/typescript/src/core/assets.d.ts +1 -0
- package/lib/typescript/src/core/assets.d.ts.map +1 -1
- package/lib/typescript/src/core/router.d.ts +1 -0
- package/lib/typescript/src/core/router.d.ts.map +1 -1
- package/lib/typescript/src/core/routes.d.ts.map +1 -1
- package/lib/typescript/src/core/storage.d.ts +11 -3
- package/lib/typescript/src/core/storage.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations.d.ts +40 -0
- package/lib/typescript/src/l10n/app_localizations.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_en.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_fr.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_pt.d.ts.map +1 -1
- package/lib/typescript/src/modules/categorie/presentation/pages/categorie_select_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/compte/presentation/pages/solde_widget_with_qr.d.ts.map +1 -1
- package/lib/typescript/src/modules/home/presentation/pages/home_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/home/presentation/pages/home_tab_compte_more_menu.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_bloc.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts +5 -0
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/pages/notification_page_liste.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/web/profile_web_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts +70 -0
- package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts +21 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts +21 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts +29 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts.map +1 -0
- package/lib/typescript/src/modules/subscription/presentation/pages/subscription_menu_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts +5 -0
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts +5 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/amount_widget.d.ts +3 -1
- package/lib/typescript/src/shared/widgets/amount_widget.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/input_text.d.ts +3 -3
- package/lib/typescript/src/shared/widgets/input_text.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts +5 -1
- package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts +7 -0
- package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts.map +1 -0
- package/package.json +49 -35
- package/src/core/assets.ts +1 -0
- package/src/core/router.tsx +3 -0
- package/src/core/routes.tsx +15 -0
- package/src/core/storage.ts +30 -22
- package/src/l10n/app_localizations.ts +40 -0
- package/src/l10n/app_localizations_en.ts +20 -0
- package/src/l10n/app_localizations_fr.ts +20 -0
- package/src/l10n/app_localizations_pt.ts +20 -0
- package/src/modules/alias/presentation/pages/alias_page_create_options.tsx +3 -3
- package/src/modules/alias/presentation/pages/alias_page_otp.tsx +2 -2
- package/src/modules/categorie/presentation/pages/categorie_edit_icon_btn.tsx +2 -2
- package/src/modules/categorie/presentation/pages/categorie_select_widget.tsx +13 -9
- package/src/modules/compte/presentation/pages/solde_widget_with_qr.tsx +10 -3
- package/src/modules/home/presentation/pages/home_page.tsx +31 -2
- package/src/modules/home/presentation/pages/home_tab_compte_more_menu.tsx +10 -5
- package/src/modules/notification/presentation/bloc/notification_bloc.ts +12 -3
- package/src/modules/notification/presentation/bloc/notification_state.ts +11 -1
- package/src/modules/notification/presentation/pages/notification_page_liste.tsx +4 -1
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx +2 -2
- package/src/modules/profile/presentation/pages/web/profile_web_page.tsx +3 -2
- package/src/modules/qrcode/presentation/pages/qrcode_alias_page.tsx +2 -2
- package/src/modules/qrcode/presentation/pages/qrcode_alias_page_share.tsx +2 -2
- package/src/modules/search/domain/models/fonctionnalite.ts +217 -0
- package/src/modules/search/presentation/pages/search_fonctionnalites.ts +135 -0
- package/src/modules/search/presentation/pages/search_page.tsx +314 -0
- package/src/modules/search/presentation/pages/search_page_voice_button.tsx +283 -0
- package/src/modules/subscription/presentation/pages/subscription_menu_widget.tsx +9 -4
- package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.ts +15 -1
- package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.ts +10 -1
- package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.tsx +204 -101
- package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.tsx +4 -1
- package/src/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.tsx +3 -3
- package/src/shared/widgets/amount_widget.tsx +5 -2
- package/src/shared/widgets/input_amount.tsx +2 -2
- package/src/shared/widgets/input_text.tsx +8 -3
- package/src/shared/widgets/menu_actions_widget.tsx +8 -2
- package/src/shared/widgets/pagination_loader_widget.tsx +24 -0
package/src/core/storage.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
-
import
|
|
2
|
+
import { cbc } from '@noble/ciphers/aes.js';
|
|
3
3
|
import * as Crypto from 'expo-crypto';
|
|
4
4
|
import * as SecureStore from 'expo-secure-store';
|
|
5
5
|
|
|
@@ -95,7 +95,7 @@ export class Box {
|
|
|
95
95
|
constructor(
|
|
96
96
|
readonly name: string,
|
|
97
97
|
private readonly entries: Map<string, Item>,
|
|
98
|
-
private readonly dek:
|
|
98
|
+
private readonly dek: Uint8Array,
|
|
99
99
|
) {}
|
|
100
100
|
|
|
101
101
|
get length(): number {
|
|
@@ -142,7 +142,7 @@ export class Box {
|
|
|
142
142
|
return `${KEY_PREFIX}box.${name}`;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
static async open(name: string, dek:
|
|
145
|
+
static async open(name: string, dek: Uint8Array): Promise<Box> {
|
|
146
146
|
const raw = await AsyncStorage.getItem(Box.storageKey(name));
|
|
147
147
|
let entries = new Map<string, Item>();
|
|
148
148
|
if (raw != null) {
|
|
@@ -159,26 +159,34 @@ export class Box {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Chiffre en AES-256-CBC (padding PKCS7) : `<iv base64>:<texte chiffré base64>`.
|
|
164
|
+
* Même format que l'ancienne implémentation `crypto-js` : les données déjà
|
|
165
|
+
* enregistrées restent lisibles.
|
|
166
|
+
*/
|
|
167
|
+
export function encrypt(plain: string, key: Uint8Array): string {
|
|
168
|
+
const iv = Crypto.getRandomBytes(16);
|
|
169
|
+
const ciphertext = cbc(key, iv).encrypt(new TextEncoder().encode(plain));
|
|
170
|
+
return `${toBase64(iv)}:${toBase64(ciphertext)}`;
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
function decrypt(payload: string, key:
|
|
173
|
+
export function decrypt(payload: string, key: Uint8Array): string {
|
|
169
174
|
const [ivB64, cipherB64] = payload.split(':');
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
const plain = CryptoJS.AES.decrypt(params, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
|
|
173
|
-
return plain.toString(CryptoJS.enc.Utf8);
|
|
175
|
+
const plain = cbc(key, fromBase64(ivB64)).decrypt(fromBase64(cipherB64));
|
|
176
|
+
return new TextDecoder().decode(plain);
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
for (let i = 0; i < bytes.length; i++)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
export function toBase64(bytes: Uint8Array): string {
|
|
180
|
+
let binary = '';
|
|
181
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
182
|
+
return btoa(binary);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function fromBase64(value: string): Uint8Array {
|
|
186
|
+
const binary = atob(value);
|
|
187
|
+
const bytes = new Uint8Array(binary.length);
|
|
188
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
189
|
+
return bytes;
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
/**
|
|
@@ -195,7 +203,7 @@ export class AppStorage {
|
|
|
195
203
|
static readonly keyId = 'DEK';
|
|
196
204
|
|
|
197
205
|
/** The Data Encryption Key */
|
|
198
|
-
private static dek:
|
|
206
|
+
private static dek: Uint8Array;
|
|
199
207
|
|
|
200
208
|
/** Collections ouvertes (chargées entièrement en mémoire, comme les boîtes Hive). */
|
|
201
209
|
static collections = new Map<string, Promise<Box>>();
|
|
@@ -206,12 +214,12 @@ export class AppStorage {
|
|
|
206
214
|
const keyString = await secureStorage.read(AppStorage.keyId);
|
|
207
215
|
if (keyString == null) {
|
|
208
216
|
const key = Crypto.getRandomBytes(32);
|
|
209
|
-
await secureStorage.write(AppStorage.keyId,
|
|
210
|
-
AppStorage.dek =
|
|
217
|
+
await secureStorage.write(AppStorage.keyId, toBase64(key));
|
|
218
|
+
AppStorage.dek = key;
|
|
211
219
|
}
|
|
212
220
|
// Sinon récupérer la clé existante
|
|
213
221
|
else {
|
|
214
|
-
AppStorage.dek =
|
|
222
|
+
AppStorage.dek = fromBase64(keyString);
|
|
215
223
|
}
|
|
216
224
|
AppStorage.isInit = true;
|
|
217
225
|
}
|
|
@@ -1027,6 +1027,8 @@ export interface AppLocalizations {
|
|
|
1027
1027
|
readonly transactionSearchInputFilterCategoriesSensPayes: string;
|
|
1028
1028
|
/** Apply */
|
|
1029
1029
|
readonly transactionSearchInputFilterBtnAppliquer: string;
|
|
1030
|
+
/** Reset */
|
|
1031
|
+
readonly transactionSearchInputFilterBtnReinitialiser: string;
|
|
1030
1032
|
/** Scan */
|
|
1031
1033
|
readonly qrcodePageBtnScan: string;
|
|
1032
1034
|
/** QR Code */
|
|
@@ -1343,6 +1345,44 @@ export interface AppLocalizations {
|
|
|
1343
1345
|
notificationMAJMessage(systeme: string): string;
|
|
1344
1346
|
/** Install */
|
|
1345
1347
|
readonly notificationMAJBtn: string;
|
|
1348
|
+
/** What would you like to do? */
|
|
1349
|
+
readonly searchTitle: string;
|
|
1350
|
+
/** Search for a feature */
|
|
1351
|
+
readonly searchHint: string;
|
|
1352
|
+
/** No feature matches “{motCle}” */
|
|
1353
|
+
searchEmpty(motCle: string): string;
|
|
1354
|
+
/** Send money to a contact */
|
|
1355
|
+
readonly searchSendSubTitle: string;
|
|
1356
|
+
/** Request money from a contact */
|
|
1357
|
+
readonly searchReceiveSubTitle: string;
|
|
1358
|
+
/** Scan a QR code */
|
|
1359
|
+
readonly searchQrScanTitle: string;
|
|
1360
|
+
/** Pay by scanning a QR code */
|
|
1361
|
+
readonly searchQrScanSubTitle: string;
|
|
1362
|
+
/** Receive money with your QR code */
|
|
1363
|
+
readonly searchQrShowSubTitle: string;
|
|
1364
|
+
/** Find a payment sent or received */
|
|
1365
|
+
readonly searchTransactionsSubTitle: string;
|
|
1366
|
+
/** See your scheduled payments */
|
|
1367
|
+
readonly searchAbonnementsSubTitle: string;
|
|
1368
|
+
/** Your details and your account */
|
|
1369
|
+
readonly searchCompteSubTitle: string;
|
|
1370
|
+
/** Theme, notifications and home screen */
|
|
1371
|
+
readonly searchParametreSubTitle: string;
|
|
1372
|
+
/** Manage your contacts and aliases */
|
|
1373
|
+
readonly searchContactsSubTitle: string;
|
|
1374
|
+
/** Frequently asked questions and support */
|
|
1375
|
+
readonly searchAideSubTitle: string;
|
|
1376
|
+
/** Voice search */
|
|
1377
|
+
readonly searchVoiceStart: string;
|
|
1378
|
+
/** Stop listening */
|
|
1379
|
+
readonly searchVoiceStop: string;
|
|
1380
|
+
/** Listening… */
|
|
1381
|
+
readonly searchVoiceListening: string;
|
|
1382
|
+
/** Tell me what you would like to do */
|
|
1383
|
+
readonly searchVoicePrompt: string;
|
|
1384
|
+
/** Voice search is not available. Check microphone access in settings. */
|
|
1385
|
+
readonly searchVoiceUnavailable: string;
|
|
1346
1386
|
}
|
|
1347
1387
|
|
|
1348
1388
|
export const supportedLocales = ['fr', 'en', 'pt'] as const;
|
|
@@ -524,6 +524,7 @@ Please try again later.`,
|
|
|
524
524
|
transactionSearchInputFilterCategoriesSensRecus: `Received`,
|
|
525
525
|
transactionSearchInputFilterCategoriesSensPayes: `Sent`,
|
|
526
526
|
transactionSearchInputFilterBtnAppliquer: `Apply`,
|
|
527
|
+
transactionSearchInputFilterBtnReinitialiser: `Reset`,
|
|
527
528
|
qrcodePageBtnScan: `Scan`,
|
|
528
529
|
qrcodePageBtnMonCode: `QR Code`,
|
|
529
530
|
qrcodePageBtnMonAlias: `My alias`,
|
|
@@ -683,4 +684,23 @@ Scanning will be automatic`,
|
|
|
683
684
|
notificationMAJDate: (date: string) => `Since ${date}`,
|
|
684
685
|
notificationMAJMessage: (systeme: string) => `A new version of the application is available on ${systeme}`,
|
|
685
686
|
notificationMAJBtn: `Install`,
|
|
687
|
+
searchTitle: `What would you like to do?`,
|
|
688
|
+
searchHint: `Search for a feature`,
|
|
689
|
+
searchEmpty: (motCle: string) => `No feature matches “${motCle}”`,
|
|
690
|
+
searchSendSubTitle: `Send money to a contact`,
|
|
691
|
+
searchReceiveSubTitle: `Request money from a contact`,
|
|
692
|
+
searchQrScanTitle: `Scan a QR code`,
|
|
693
|
+
searchQrScanSubTitle: `Pay by scanning a QR code`,
|
|
694
|
+
searchQrShowSubTitle: `Receive money with your QR code`,
|
|
695
|
+
searchTransactionsSubTitle: `Find a payment sent or received`,
|
|
696
|
+
searchAbonnementsSubTitle: `See your scheduled payments`,
|
|
697
|
+
searchCompteSubTitle: `Your details and your account`,
|
|
698
|
+
searchParametreSubTitle: `Theme, notifications and home screen`,
|
|
699
|
+
searchContactsSubTitle: `Manage your contacts and aliases`,
|
|
700
|
+
searchAideSubTitle: `Frequently asked questions and support`,
|
|
701
|
+
searchVoiceStart: `Voice search`,
|
|
702
|
+
searchVoiceStop: `Stop listening`,
|
|
703
|
+
searchVoiceListening: `Listening…`,
|
|
704
|
+
searchVoicePrompt: `Tell me what you would like to do`,
|
|
705
|
+
searchVoiceUnavailable: `Voice search is not available. Check microphone access in settings.`,
|
|
686
706
|
};
|
|
@@ -523,6 +523,7 @@ Merci de votre compréhension.`,
|
|
|
523
523
|
transactionSearchInputFilterCategoriesSensRecus: `Reçus`,
|
|
524
524
|
transactionSearchInputFilterCategoriesSensPayes: `Envoyés`,
|
|
525
525
|
transactionSearchInputFilterBtnAppliquer: `Appliquer`,
|
|
526
|
+
transactionSearchInputFilterBtnReinitialiser: `Réinitialiser`,
|
|
526
527
|
qrcodePageBtnScan: `Scan`,
|
|
527
528
|
qrcodePageBtnMonCode: `Mon Code`,
|
|
528
529
|
qrcodePageBtnMonAlias: `Mon alias`,
|
|
@@ -682,4 +683,23 @@ Le scan se fait automatiquement`,
|
|
|
682
683
|
notificationMAJDate: (date: string) => `Depuis le ${date}`,
|
|
683
684
|
notificationMAJMessage: (systeme: string) => `Une nouvelle version de l'application est disponible sur ${systeme}`,
|
|
684
685
|
notificationMAJBtn: `Installer`,
|
|
686
|
+
searchTitle: `Que voulez-vous faire ?`,
|
|
687
|
+
searchHint: `Rechercher une fonctionnalité`,
|
|
688
|
+
searchEmpty: (motCle: string) => `Aucune fonctionnalité ne correspond à « ${motCle} »`,
|
|
689
|
+
searchSendSubTitle: `Envoyer de l’argent à un contact`,
|
|
690
|
+
searchReceiveSubTitle: `Demander de l’argent à un contact`,
|
|
691
|
+
searchQrScanTitle: `Scanner un QR code`,
|
|
692
|
+
searchQrScanSubTitle: `Payer en scannant un QR code`,
|
|
693
|
+
searchQrShowSubTitle: `Recevoir de l’argent avec votre QR code`,
|
|
694
|
+
searchTransactionsSubTitle: `Retrouver un paiement envoyé ou reçu`,
|
|
695
|
+
searchAbonnementsSubTitle: `Voir vos paiements programmés`,
|
|
696
|
+
searchCompteSubTitle: `Vos informations et votre compte`,
|
|
697
|
+
searchParametreSubTitle: `Thème, notifications et écran d’accueil`,
|
|
698
|
+
searchContactsSubTitle: `Gérer vos contacts et vos alias`,
|
|
699
|
+
searchAideSubTitle: `Questions fréquentes et assistance`,
|
|
700
|
+
searchVoiceStart: `Recherche vocale`,
|
|
701
|
+
searchVoiceStop: `Arrêter l’écoute`,
|
|
702
|
+
searchVoiceListening: `Je vous écoute…`,
|
|
703
|
+
searchVoicePrompt: `Dites-moi ce que vous souhaitez faire`,
|
|
704
|
+
searchVoiceUnavailable: `La recherche vocale n’est pas disponible. Vérifiez l’accès au micro dans les réglages.`,
|
|
685
705
|
};
|
|
@@ -524,6 +524,7 @@ Por favor, tente novamente mais tarde`,
|
|
|
524
524
|
transactionSearchInputFilterCategoriesSensRecus: `Recebidos`,
|
|
525
525
|
transactionSearchInputFilterCategoriesSensPayes: `Enviados`,
|
|
526
526
|
transactionSearchInputFilterBtnAppliquer: `Aplicar`,
|
|
527
|
+
transactionSearchInputFilterBtnReinitialiser: `Repor`,
|
|
527
528
|
qrcodePageBtnScan: `Scan`,
|
|
528
529
|
qrcodePageBtnMonCode: `Meu Código`,
|
|
529
530
|
qrcodePageBtnMonAlias: `Meu alias`,
|
|
@@ -683,4 +684,23 @@ O scan é feito automaticamente`,
|
|
|
683
684
|
notificationMAJDate: (date: string) => `Desde ${date}`,
|
|
684
685
|
notificationMAJMessage: (systeme: string) => `Uma nova versão da aplicação está disponível em ${systeme}`,
|
|
685
686
|
notificationMAJBtn: `Instalar`,
|
|
687
|
+
searchTitle: `O que deseja fazer?`,
|
|
688
|
+
searchHint: `Pesquisar uma funcionalidade`,
|
|
689
|
+
searchEmpty: (motCle: string) => `Nenhuma funcionalidade corresponde a «${motCle}»`,
|
|
690
|
+
searchSendSubTitle: `Enviar dinheiro a um contacto`,
|
|
691
|
+
searchReceiveSubTitle: `Pedir dinheiro a um contacto`,
|
|
692
|
+
searchQrScanTitle: `Digitalizar um código QR`,
|
|
693
|
+
searchQrScanSubTitle: `Pagar digitalizando um código QR`,
|
|
694
|
+
searchQrShowSubTitle: `Receber dinheiro com o seu código QR`,
|
|
695
|
+
searchTransactionsSubTitle: `Encontrar um pagamento enviado ou recebido`,
|
|
696
|
+
searchAbonnementsSubTitle: `Ver os seus pagamentos programados`,
|
|
697
|
+
searchCompteSubTitle: `Os seus dados e a sua conta`,
|
|
698
|
+
searchParametreSubTitle: `Tema, notificações e ecrã inicial`,
|
|
699
|
+
searchContactsSubTitle: `Gerir os seus contactos e alias`,
|
|
700
|
+
searchAideSubTitle: `Perguntas frequentes e assistência`,
|
|
701
|
+
searchVoiceStart: `Pesquisa por voz`,
|
|
702
|
+
searchVoiceStop: `Parar de ouvir`,
|
|
703
|
+
searchVoiceListening: `Estou a ouvir…`,
|
|
704
|
+
searchVoicePrompt: `Diga-me o que deseja fazer`,
|
|
705
|
+
searchVoiceUnavailable: `A pesquisa por voz não está disponível. Verifique o acesso ao microfone nas definições.`,
|
|
686
706
|
};
|
|
@@ -4,7 +4,7 @@ import { Images } from '../../../../core/assets';
|
|
|
4
4
|
import { useBloc } from '../../../../core/bloc_react';
|
|
5
5
|
import { useTheme } from '../../../../core/theme';
|
|
6
6
|
import { useAppLocalizations } from '../../../../l10n/localizations';
|
|
7
|
-
import { MenuActionItem,
|
|
7
|
+
import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
|
|
8
8
|
import { MyPageContainer } from '../../../../shared/widgets/my_page_container';
|
|
9
9
|
import { AliasBloc } from '../bloc/alias_bloc';
|
|
10
10
|
import { CreateAliasMBNOEvent, CreateAliasSHIDEvent } from '../bloc/alias_event';
|
|
@@ -26,7 +26,7 @@ export function AliasPageCreateOptions({ compte }: { compte: string }) {
|
|
|
26
26
|
<View style={styles.gap32} />
|
|
27
27
|
|
|
28
28
|
{/* Bouton créer alias SHID */}
|
|
29
|
-
<
|
|
29
|
+
<MenuActionsWidget
|
|
30
30
|
items={[
|
|
31
31
|
new MenuActionItem(
|
|
32
32
|
Images.aliasIconUser,
|
|
@@ -40,7 +40,7 @@ export function AliasPageCreateOptions({ compte }: { compte: string }) {
|
|
|
40
40
|
<View style={styles.gap20} />
|
|
41
41
|
|
|
42
42
|
{/* - type MBNO */}
|
|
43
|
-
<
|
|
43
|
+
<MenuActionsWidget
|
|
44
44
|
items={[
|
|
45
45
|
new MenuActionItem(
|
|
46
46
|
Images.aliasIconPhone,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef, useState, type ReactNode } from 'react';
|
|
2
|
-
import { Keyboard, StyleSheet, Text, TextInput, useWindowDimensions, View } from 'react-native';
|
|
2
|
+
import { Keyboard, StyleSheet, Text, TextInput, useWindowDimensions, View, type TextInputInstance } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { useTheme } from '../../../../core/theme';
|
|
5
5
|
|
|
@@ -30,7 +30,7 @@ export function AliasPageOtp({
|
|
|
30
30
|
const theme = useTheme();
|
|
31
31
|
const { width: largeur } = useWindowDimensions();
|
|
32
32
|
const [values, setValues] = useState<string[]>(() => Array(pinLength).fill(''));
|
|
33
|
-
const inputs = useRef<(
|
|
33
|
+
const inputs = useRef<(TextInputInstance | null)[]>([]);
|
|
34
34
|
|
|
35
35
|
const isTablet = largeur >= 600;
|
|
36
36
|
const paddingX = 20;
|
|
@@ -12,7 +12,7 @@ import { ElevatedButton } from '../../../../shared/material/buttons';
|
|
|
12
12
|
import { Card } from '../../../../shared/material/card';
|
|
13
13
|
import { showModalBottomSheet } from '../../../../shared/material/overlays';
|
|
14
14
|
import { EmojiSelectWidget } from '../../../../shared/widgets/emoji_select_widget';
|
|
15
|
-
import { MenuActionItem,
|
|
15
|
+
import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
|
|
16
16
|
import { CategorieIconType } from '../../domain/models/categorie';
|
|
17
17
|
import { CategorieBloc } from '../bloc/categorie_bloc';
|
|
18
18
|
import { CategorieAddValidationEvent, CategorieEditValidationEvent } from '../bloc/categorie_event';
|
|
@@ -67,7 +67,7 @@ function CategorieEditIconOptions() {
|
|
|
67
67
|
<ScrollView contentContainerStyle={styles.sheetContent}>
|
|
68
68
|
<Text style={[theme.textTheme.headlineMedium, styles.title]}>{traductions.categorieFormIconSheetTitle}</Text>
|
|
69
69
|
<Card>
|
|
70
|
-
<
|
|
70
|
+
<MenuActionsWidget
|
|
71
71
|
items={[
|
|
72
72
|
// Emoji
|
|
73
73
|
new MenuActionItem(Images.categorieIconEmoji, traductions.categorieFormIconSheetEmojiTitle, null, () => void selectEmoji(), {
|
|
@@ -17,14 +17,18 @@ export function CategorieSelectWidget({
|
|
|
17
17
|
const state = useBlocBuilder(categorieBloc);
|
|
18
18
|
return (
|
|
19
19
|
<View>
|
|
20
|
-
{/* Catégories personnalisées */}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
{/* Catégories personnalisées (pas de carte vide s'il n'y en a pas) */}
|
|
21
|
+
{state.categories.length > 0 && (
|
|
22
|
+
<>
|
|
23
|
+
<CategorieListeWidget
|
|
24
|
+
liste={state.categories}
|
|
25
|
+
editable={false}
|
|
26
|
+
defaultSelectedCategories={selectedCategories}
|
|
27
|
+
onSelectionChanged={onSelectionChanged}
|
|
28
|
+
/>
|
|
29
|
+
<View style={styles.gap16} />
|
|
30
|
+
</>
|
|
31
|
+
)}
|
|
28
32
|
{/* Catégories par défaut */}
|
|
29
33
|
<CategorieListeWidget
|
|
30
34
|
liste={Categorie.defaultListe}
|
|
@@ -35,4 +39,4 @@ export function CategorieSelectWidget({
|
|
|
35
39
|
);
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
const styles = StyleSheet.create({
|
|
42
|
+
const styles = StyleSheet.create({ gap16: { height: 16 } });
|
|
@@ -157,8 +157,6 @@ function SoldeRow({ soldeState, compteSoldeBloc }: { soldeState: CompteSoldeStat
|
|
|
157
157
|
const traductions = useAppLocalizations();
|
|
158
158
|
const configBloc = useBloc(ConfigBloc);
|
|
159
159
|
const displayEye = configBloc.state.configParams.params[ConfigKey.hideEye] !== '1';
|
|
160
|
-
const solde = soldeState.solde ?? 0;
|
|
161
|
-
const fontSize = solde < 100000000 ? 24 : solde < 1000000000 ? 20 : 18;
|
|
162
160
|
|
|
163
161
|
return (
|
|
164
162
|
<View>
|
|
@@ -167,7 +165,15 @@ function SoldeRow({ soldeState, compteSoldeBloc }: { soldeState: CompteSoldeStat
|
|
|
167
165
|
{soldeState instanceof CompteSoldeStateInitial && <SkeletonWidget height={25} width={150} />}
|
|
168
166
|
{soldeState instanceof CompteSoldeDisplayState && (
|
|
169
167
|
<>
|
|
170
|
-
|
|
168
|
+
{/* Le montant prend la place disponible et se réduit s'il est trop long (8 chiffres et plus), au lieu de déborder */}
|
|
169
|
+
<View style={styles.amount}>
|
|
170
|
+
<AmountWidget
|
|
171
|
+
montant={soldeState.solde}
|
|
172
|
+
numberOfLines={1}
|
|
173
|
+
adjustsFontSizeToFit
|
|
174
|
+
style={{ ...theme.textTheme.headlineLarge, fontSize: 24 }}
|
|
175
|
+
/>
|
|
176
|
+
</View>
|
|
171
177
|
{displayEye && <EyeButton compteSoldeBloc={compteSoldeBloc} />}
|
|
172
178
|
</>
|
|
173
179
|
)}
|
|
@@ -281,6 +287,7 @@ const styles = StyleSheet.create({
|
|
|
281
287
|
cardContent: { paddingLeft: 10, paddingRight: 10, paddingTop: 16, paddingBottom: 10 },
|
|
282
288
|
row: { flexDirection: 'row', alignItems: 'flex-start' },
|
|
283
289
|
inlineRow: { flexDirection: 'row', alignItems: 'center' },
|
|
290
|
+
amount: { flexShrink: 1 },
|
|
284
291
|
expanded: { flex: 1 },
|
|
285
292
|
libelle: { color: Themer.neural04Color, letterSpacing: 0.4 },
|
|
286
293
|
bold: { fontWeight: '600' },
|
|
@@ -16,6 +16,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
16
16
|
import { useBloc } from '../../../../core/bloc_react';
|
|
17
17
|
import { CompteAliasStatus } from '../../../../core/compte_alias_status';
|
|
18
18
|
import { Di } from '../../../../core/di';
|
|
19
|
+
import { AppRouter, useAppRouter } from '../../../../core/router';
|
|
19
20
|
import { Themer, useTheme } from '../../../../core/theme';
|
|
20
21
|
import { useValueListenable } from '../../../../core/value_notifier';
|
|
21
22
|
import { useAppLocalizations } from '../../../../l10n/localizations';
|
|
@@ -24,6 +25,8 @@ import { ContactBloc } from '../../../contacts/presentation/bloc/contact_bloc';
|
|
|
24
25
|
import { ContactListEvent } from '../../../contacts/presentation/bloc/contact_event';
|
|
25
26
|
import { ParametreHideAmountBloc } from '../../../profile/presentation/bloc/hide_amount/hide_amount_bloc';
|
|
26
27
|
import { ParametreHideAmountInitEvent } from '../../../profile/presentation/bloc/hide_amount/hide_amount_event';
|
|
28
|
+
import type { Fonctionnalite } from '../../../search/domain/models/fonctionnalite';
|
|
29
|
+
import { fonctionnalitesRecherchables } from '../../../search/presentation/pages/search_fonctionnalites';
|
|
27
30
|
import { TransactionSendBloc } from '../../../transactions/presentation/bloc/transaction_send/transaction_send_bloc';
|
|
28
31
|
import { TransactionSendUpdateStateEvent } from '../../../transactions/presentation/bloc/transaction_send/transaction_send_event';
|
|
29
32
|
import { HomeTabCompte } from './home_tab_compte';
|
|
@@ -36,6 +39,7 @@ import { ComingSoon } from './pending_modules';
|
|
|
36
39
|
export function HomePage({ selectedTab }: { selectedTab?: number }) {
|
|
37
40
|
const traductions = useAppLocalizations();
|
|
38
41
|
const theme = useTheme();
|
|
42
|
+
const router = useAppRouter();
|
|
39
43
|
const insets = useSafeAreaInsets();
|
|
40
44
|
const { width } = useWindowDimensions();
|
|
41
45
|
const status = useValueListenable(Di.activeCompteAlias);
|
|
@@ -74,6 +78,17 @@ export function HomePage({ selectedTab }: { selectedTab?: number }) {
|
|
|
74
78
|
listRef.current?.scrollToIndex({ index, animated: true });
|
|
75
79
|
};
|
|
76
80
|
|
|
81
|
+
/**
|
|
82
|
+
* La recherche renvoie la fonctionnalité choisie au lieu de l'ouvrir :
|
|
83
|
+
* la navigation part de l'accueil, et le retour y ramène.
|
|
84
|
+
*/
|
|
85
|
+
const ouvrirRecherche = async () => {
|
|
86
|
+
const choisie = await AppRouter.push<Fonctionnalite>(router, AppRouter.search, {
|
|
87
|
+
params: fonctionnalitesRecherchables(router, traductions, { ouvrirOnglet: goToTab }),
|
|
88
|
+
});
|
|
89
|
+
choisie?.ouvrir();
|
|
90
|
+
};
|
|
91
|
+
|
|
77
92
|
const onMomentumScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
78
93
|
setTab(Math.round(e.nativeEvent.contentOffset.x / pageWidth));
|
|
79
94
|
};
|
|
@@ -91,8 +106,20 @@ export function HomePage({ selectedTab }: { selectedTab?: number }) {
|
|
|
91
106
|
<View style={{ backgroundColor: theme.appBarTheme.backgroundColor }}>
|
|
92
107
|
<View style={styles.toolbar}>
|
|
93
108
|
<HomeToolbarLeading user={Di.config.user} enabled={hasAlias} />
|
|
94
|
-
{
|
|
95
|
-
|
|
109
|
+
<View style={styles.actions}>
|
|
110
|
+
{/* Recherche dans les fonctionnalités (grisé si le compte actif n'a pas d'alias) */}
|
|
111
|
+
<Pressable
|
|
112
|
+
accessibilityRole="button"
|
|
113
|
+
accessibilityLabel={traductions.searchTitle}
|
|
114
|
+
disabled={!hasAlias}
|
|
115
|
+
onPress={() => void ouvrirRecherche()}
|
|
116
|
+
style={[styles.action, { opacity: hasAlias ? 1 : 0.38 }]}
|
|
117
|
+
>
|
|
118
|
+
<MaterialIcons name="search" size={24} color={theme.appBarTheme.foregroundColor} />
|
|
119
|
+
</Pressable>
|
|
120
|
+
{/* Notifications (grisé si le compte actif n'a pas d'alias) */}
|
|
121
|
+
<NotificationBtnOpenWidget enabled={hasAlias} />
|
|
122
|
+
</View>
|
|
96
123
|
</View>
|
|
97
124
|
{/* Onglets */}
|
|
98
125
|
<View style={styles.tabBar}>
|
|
@@ -161,6 +188,8 @@ export function HomePage({ selectedTab }: { selectedTab?: number }) {
|
|
|
161
188
|
|
|
162
189
|
const styles = StyleSheet.create({
|
|
163
190
|
toolbar: { height: 56, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
191
|
+
actions: { flexDirection: 'row', alignItems: 'center' },
|
|
192
|
+
action: { padding: 12 },
|
|
164
193
|
tabBar: { flexDirection: 'row', paddingHorizontal: 20, height: 46, alignItems: 'center' },
|
|
165
194
|
tab: { flex: 1, height: 36, marginHorizontal: 2, alignItems: 'center', justifyContent: 'center', borderRadius: 12.2 },
|
|
166
195
|
tabSelected: { backgroundColor: '#151413' },
|
|
@@ -6,7 +6,7 @@ import { AppRouter, useAppRouter } from '../../../../core/router';
|
|
|
6
6
|
import { useTheme } from '../../../../core/theme';
|
|
7
7
|
import { useAppLocalizations } from '../../../../l10n/localizations';
|
|
8
8
|
import { Card } from '../../../../shared/material/card';
|
|
9
|
-
import { MenuActionItem,
|
|
9
|
+
import { MenuActionItem, MenuActionsWidget } from '../../../../shared/widgets/menu_actions_widget';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Menu « Plus » de l'accueil. Le participant peut personnaliser ce menu
|
|
@@ -18,6 +18,11 @@ export function HomeTabCompteMoreMenu() {
|
|
|
18
18
|
const router = useAppRouter();
|
|
19
19
|
const { height } = useWindowDimensions();
|
|
20
20
|
const insets = useSafeAreaInsets();
|
|
21
|
+
// Fermer le menu avant d'ouvrir la page : sinon il reste affiché par-dessus
|
|
22
|
+
const open = (location: string) => {
|
|
23
|
+
AppRouter.pop(router);
|
|
24
|
+
void AppRouter.push(router, location);
|
|
25
|
+
};
|
|
21
26
|
return (
|
|
22
27
|
<View
|
|
23
28
|
style={[
|
|
@@ -27,14 +32,14 @@ export function HomeTabCompteMoreMenu() {
|
|
|
27
32
|
>
|
|
28
33
|
<Card>
|
|
29
34
|
<ScrollView>
|
|
30
|
-
<
|
|
35
|
+
<MenuActionsWidget
|
|
31
36
|
items={[
|
|
32
37
|
// Programmer un paiement
|
|
33
38
|
new MenuActionItem(
|
|
34
39
|
Images.homeFooterTransfer,
|
|
35
40
|
traductions.homeActionMoreSheetProgrammerTitle,
|
|
36
41
|
traductions.homeActionMoreSheetProgrammerSubTitle,
|
|
37
|
-
() =>
|
|
42
|
+
() => open(AppRouter.subscriptionSchedule),
|
|
38
43
|
{ iconSize: 20 },
|
|
39
44
|
),
|
|
40
45
|
// Trouver un abonnement
|
|
@@ -42,7 +47,7 @@ export function HomeTabCompteMoreMenu() {
|
|
|
42
47
|
Images.homeMoreFindSubscription,
|
|
43
48
|
traductions.homeActionMoreSheetAbonnementTitle,
|
|
44
49
|
traductions.homeActionMoreSheetAbonnementSubTitle,
|
|
45
|
-
() =>
|
|
50
|
+
() => open(AppRouter.subscriptionSubscribe),
|
|
46
51
|
{ iconSize: 20 },
|
|
47
52
|
),
|
|
48
53
|
// Partager dépenses
|
|
@@ -50,7 +55,7 @@ export function HomeTabCompteMoreMenu() {
|
|
|
50
55
|
Images.homeMoreSplitPayments,
|
|
51
56
|
traductions.homeActionMoreSheetPartagerTitle,
|
|
52
57
|
traductions.homeActionMoreSheetPartagerSubTitle,
|
|
53
|
-
() =>
|
|
58
|
+
() => open(AppRouter.transactionSplitPaymentTx),
|
|
54
59
|
{ iconSize: 20 },
|
|
55
60
|
),
|
|
56
61
|
]}
|
|
@@ -147,8 +147,13 @@ export class NotificationBloc extends Bloc<NotificationEvent, NotificationState>
|
|
|
147
147
|
private async onList(event: NotificationListEvent, emit: Emitter<NotificationState>): Promise<void> {
|
|
148
148
|
// En cours de rechargement
|
|
149
149
|
emit(new NotificationLoadingState(emptyListe(), this.state.count, event.command));
|
|
150
|
+
|
|
151
|
+
// Repartir de la première page
|
|
152
|
+
const command = event.command;
|
|
153
|
+
command.index = null;
|
|
154
|
+
command.total = 0;
|
|
155
|
+
|
|
150
156
|
try {
|
|
151
|
-
const command = event.command;
|
|
152
157
|
|
|
153
158
|
// Rechercher d'abord en local et afficher
|
|
154
159
|
const donneesLocales = await this.pNotificationInputPort.list(listParams(command));
|
|
@@ -186,7 +191,8 @@ export class NotificationBloc extends Bloc<NotificationEvent, NotificationState>
|
|
|
186
191
|
if (this.paginating) return;
|
|
187
192
|
this.paginating = true;
|
|
188
193
|
const command = this.state.command;
|
|
189
|
-
|
|
194
|
+
// Affiche le loader en bas de la liste
|
|
195
|
+
emit(new NotificationInitialState(this.state.notifications, this.state.count, command, true));
|
|
190
196
|
try {
|
|
191
197
|
// Récupérer les notifications depuis le serveur
|
|
192
198
|
const liste = await this.pNotificationInputPort.search(listParams(command, event.index));
|
|
@@ -196,16 +202,19 @@ export class NotificationBloc extends Bloc<NotificationEvent, NotificationState>
|
|
|
196
202
|
if (newNotifications.length > 0) {
|
|
197
203
|
emit(
|
|
198
204
|
new NotificationInitialState(
|
|
199
|
-
new NotificationListe([...
|
|
205
|
+
new NotificationListe([...this.state.notifications.data, ...newNotifications], liste.meta),
|
|
200
206
|
this.state.count,
|
|
201
207
|
command,
|
|
202
208
|
),
|
|
203
209
|
);
|
|
210
|
+
return;
|
|
204
211
|
}
|
|
205
212
|
} catch (e) {
|
|
206
213
|
this.logger.e('Recuperer liste notification pagination error =>', { error: e });
|
|
207
214
|
} finally {
|
|
208
215
|
this.paginating = false;
|
|
209
216
|
}
|
|
217
|
+
// Page vide ou erreur : on retire le loader
|
|
218
|
+
emit(new NotificationInitialState(this.state.notifications, this.state.count, command));
|
|
210
219
|
}
|
|
211
220
|
}
|
|
@@ -11,7 +11,17 @@ export abstract class NotificationState {
|
|
|
11
11
|
) {}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export class NotificationInitialState extends NotificationState {
|
|
14
|
+
export class NotificationInitialState extends NotificationState {
|
|
15
|
+
constructor(
|
|
16
|
+
notifications: NotificationListe,
|
|
17
|
+
count: number,
|
|
18
|
+
command: NotificationSearchCommand,
|
|
19
|
+
/** true pendant le chargement de la page suivante : un petit loader est affiché en bas de la liste. */
|
|
20
|
+
readonly isPaginating = false,
|
|
21
|
+
) {
|
|
22
|
+
super(notifications, count, command);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
export class NotificationLoadingState extends NotificationState {}
|
|
17
27
|
|
|
@@ -6,9 +6,10 @@ import { Themer, useTheme } from '../../../../core/theme';
|
|
|
6
6
|
import { useAppLocalizations } from '../../../../l10n/localizations';
|
|
7
7
|
import { Card } from '../../../../shared/material/card';
|
|
8
8
|
import { DateFormat } from '../../../../shared/utils/date_format';
|
|
9
|
+
import { PaginationLoaderWidget } from '../../../../shared/widgets/pagination_loader_widget';
|
|
9
10
|
import { NotificationBloc } from '../bloc/notification_bloc';
|
|
10
11
|
import { NotificationListEvent, NotificationSearchPaginateEvent } from '../bloc/notification_event';
|
|
11
|
-
import { NotificationLoadingState } from '../bloc/notification_state';
|
|
12
|
+
import { NotificationInitialState, NotificationLoadingState } from '../bloc/notification_state';
|
|
12
13
|
import { groupNotifications } from './notification_groups';
|
|
13
14
|
import { NotificationPageListeEmpty } from './notification_page_liste_empty';
|
|
14
15
|
import { NotificationPageListeItem } from './notification_page_liste_item';
|
|
@@ -54,6 +55,8 @@ export function NotificationPageListe() {
|
|
|
54
55
|
if (command.canLoadMore()) bloc.add(new NotificationSearchPaginateEvent(command.index));
|
|
55
56
|
}}
|
|
56
57
|
onEndReachedThreshold={0.2}
|
|
58
|
+
// Chargement de la page suivante
|
|
59
|
+
ListFooterComponent={state instanceof NotificationInitialState && state.isPaginating ? <PaginationLoaderWidget /> : undefined}
|
|
57
60
|
renderItem={({ item }) => (
|
|
58
61
|
<View>
|
|
59
62
|
{/* Jour */}
|
package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { AppRouter, useAppRouter } from '../../../../../core/router';
|
|
|
10
10
|
import { Themer, useTheme } from '../../../../../core/theme';
|
|
11
11
|
import { useAppLocalizations } from '../../../../../l10n/localizations';
|
|
12
12
|
import { Card } from '../../../../../shared/material/card';
|
|
13
|
-
import { MenuActionItem,
|
|
13
|
+
import { MenuActionItem, MenuActionsWidget } from '../../../../../shared/widgets/menu_actions_widget';
|
|
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';
|
|
@@ -116,7 +116,7 @@ export function ProfileParametrePageNotifications() {
|
|
|
116
116
|
<View style={styles.gap20} />
|
|
117
117
|
<Text style={theme.textTheme.headlineMedium}>{traductions.appSettingPageMenuNotificationSonTitle}</Text>
|
|
118
118
|
<View style={styles.gap10} />
|
|
119
|
-
<
|
|
119
|
+
<MenuActionsWidget
|
|
120
120
|
items={[
|
|
121
121
|
new MenuActionItem(
|
|
122
122
|
Images.iconsCloche,
|
|
@@ -23,7 +23,7 @@ export function ProfileWebPage({ url }: { url: string }) {
|
|
|
23
23
|
const theme = useTheme();
|
|
24
24
|
const router = useAppRouter();
|
|
25
25
|
const insets = useSafeAreaInsets();
|
|
26
|
-
const webview = useRef<WebView
|
|
26
|
+
const webview = useRef<WebView<object>>(null);
|
|
27
27
|
const [loading, setLoading] = useState(true);
|
|
28
28
|
const [hasError, setHasError] = useState(false);
|
|
29
29
|
|
|
@@ -47,7 +47,8 @@ export function ProfileWebPage({ url }: { url: string }) {
|
|
|
47
47
|
/>
|
|
48
48
|
) : null}
|
|
49
49
|
<View style={hasError ? styles.hidden : styles.flex}>
|
|
50
|
-
|
|
50
|
+
{/* react-native-webview 14 : sans paramètre de type, ses props valent `never` */}
|
|
51
|
+
<WebView<object>
|
|
51
52
|
ref={webview}
|
|
52
53
|
source={{ uri: url }}
|
|
53
54
|
style={styles.web}
|