@forthtilliath/react-native-kit 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +161 -1
  2. package/dist/components/index.d.ts +1 -0
  3. package/dist/components/index.d.ts.map +1 -1
  4. package/dist/components/index.js +1 -0
  5. package/dist/components/settings/AboutSettingsScreen.d.ts +24 -0
  6. package/dist/components/settings/AboutSettingsScreen.d.ts.map +1 -0
  7. package/dist/components/settings/AboutSettingsScreen.js +41 -0
  8. package/dist/components/settings/BackupSettingsScreen.d.ts +52 -0
  9. package/dist/components/settings/BackupSettingsScreen.d.ts.map +1 -0
  10. package/dist/components/settings/BackupSettingsScreen.js +120 -0
  11. package/dist/components/settings/ContactSettingsScreen.d.ts +19 -0
  12. package/dist/components/settings/ContactSettingsScreen.d.ts.map +1 -0
  13. package/dist/components/settings/ContactSettingsScreen.js +39 -0
  14. package/dist/components/settings/PrivacySettingsScreen.d.ts +23 -0
  15. package/dist/components/settings/PrivacySettingsScreen.d.ts.map +1 -0
  16. package/dist/components/settings/PrivacySettingsScreen.js +51 -0
  17. package/dist/components/settings/SettingsMenu.d.ts +24 -0
  18. package/dist/components/settings/SettingsMenu.d.ts.map +1 -0
  19. package/dist/components/settings/SettingsMenu.js +40 -0
  20. package/dist/components/settings/ThemeSettingsScreen.d.ts +32 -0
  21. package/dist/components/settings/ThemeSettingsScreen.d.ts.map +1 -0
  22. package/dist/components/settings/ThemeSettingsScreen.js +71 -0
  23. package/dist/components/settings/UpdateSettingsScreen.d.ts +62 -0
  24. package/dist/components/settings/UpdateSettingsScreen.d.ts.map +1 -0
  25. package/dist/components/settings/UpdateSettingsScreen.js +178 -0
  26. package/dist/components/settings/index.d.ts +8 -0
  27. package/dist/components/settings/index.d.ts.map +1 -0
  28. package/dist/components/settings/index.js +7 -0
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +7 -0
  32. package/package.json +7 -7
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @forthtilliath/react-native-kit
2
2
 
3
- Small React Native building blocks — components, hooks, and framework-agnostic utils — with no opinion on your app's theme, distributed one file per export (grouped under `components/{picker,theme,update,list}/`, `hooks/`, `utils/{format,helpers}/`) so consumers only pull in what they use.
3
+ Small React Native building blocks — components, hooks, and framework-agnostic utils — with no opinion on your app's theme, distributed one file per export (grouped under `components/{picker,theme,update,list,settings}/`, `hooks/`, `utils/{format,helpers}/`) so consumers only pull in what they use.
4
4
 
5
5
  ## Install
6
6
 
@@ -294,6 +294,166 @@ import { UpdateAvailableBanner } from "@forthtilliath/react-native-kit/component
294
294
  }
295
295
  ```
296
296
 
297
+ ### Settings screens (`components/settings/`)
298
+
299
+ The building blocks of a typical app "Settings" tab — menu, theme, update check, backup, about, contact, privacy — factored out so each new app stops rewriting the same ~7 screens from scratch. Expo Router (or any file-based router) still needs one physical route file per screen in your app; these just give each route file almost nothing left to write beyond rendering the matching component. None of them add new peer dependencies beyond what's already listed above.
300
+
301
+ ```tsx
302
+ // app/(tabs)/settings/index.tsx
303
+ import { router } from "expo-router";
304
+ import { SettingsMenu } from "@forthtilliath/react-native-kit/components/settings/SettingsMenu";
305
+
306
+ export default function SettingsMenuScreen() {
307
+ return (
308
+ <SettingsMenu
309
+ items={[
310
+ {
311
+ key: "backup",
312
+ emoji: "💾",
313
+ title: "Sauvegarde",
314
+ hint: "Exporter / importer tes données",
315
+ onPress: () => router.push("/settings/backup"),
316
+ },
317
+ {
318
+ key: "theme",
319
+ emoji: "🎨",
320
+ title: "Thème",
321
+ hint: "Clair, sombre, ou automatique",
322
+ onPress: () => router.push("/settings/theme"),
323
+ },
324
+ {
325
+ key: "update",
326
+ emoji: "⬇️",
327
+ title: "Mise à jour",
328
+ onPress: () => router.push("/settings/update"),
329
+ },
330
+ {
331
+ key: "about",
332
+ emoji: "ℹ️",
333
+ title: "À propos",
334
+ onPress: () => router.push("/settings/about"),
335
+ },
336
+ {
337
+ key: "contact",
338
+ emoji: "✉️",
339
+ title: "Contact",
340
+ onPress: () => router.push("/settings/contact"),
341
+ },
342
+ {
343
+ key: "privacy",
344
+ emoji: "🔒",
345
+ title: "Confidentialité",
346
+ onPress: () => router.push("/settings/privacy"),
347
+ },
348
+ ]}
349
+ />
350
+ );
351
+ }
352
+ ```
353
+
354
+ `SettingsMenu` doesn't depend on expo-router (or any other navigator) itself — each item carries its own `onPress`, so it works the same with any navigation setup.
355
+
356
+ #### `<ThemeSettingsScreen value onChange />`
357
+
358
+ Same light/dark/system data contract as `ThemeToggle`/`ThemeOptionList`, but a third visual: emoji + label + a plain-text `✓` on the active row, with a hint above explaining what "Système" does. Pick whichever of the three fits your app.
359
+
360
+ ```tsx
361
+ import { ThemeSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/ThemeSettingsScreen";
362
+
363
+ <ThemeSettingsScreen value={themePreference} onChange={setThemePreference} />;
364
+ ```
365
+
366
+ #### `<UpdateSettingsScreen currentVersion checkForUpdate compareVersions downloadAndInstallApk />`
367
+
368
+ The full "check for update" screen: installed version, a manual check button, the available-update box (changelog + install button) or an up-to-date/error message, download progress, and — if `fetchReleaseHistory` is passed — a list of past releases. Checks automatically once on mount. Pairs naturally with `@forthtilliath/expo-release-updates` and `useUpdateCheck`/`UpdateAvailableBanner` above (same `checkForUpdate`/`compareVersions` you already pass those), but takes plain functions so it isn't tied to that package's exact shape.
369
+
370
+ ```tsx
371
+ import { UpdateSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/UpdateSettingsScreen";
372
+
373
+ import {
374
+ compareVersions,
375
+ downloadAndInstallApk,
376
+ fetchLatestRelease,
377
+ fetchReleaseHistory,
378
+ } from "@/lib/appUpdate";
379
+
380
+ <UpdateSettingsScreen
381
+ currentVersion={Constants.expoConfig?.version ?? "?"}
382
+ checkForUpdate={fetchLatestRelease}
383
+ compareVersions={compareVersions}
384
+ downloadAndInstallApk={downloadAndInstallApk}
385
+ fetchReleaseHistory={fetchReleaseHistory}
386
+ />;
387
+ ```
388
+
389
+ #### `<BackupSettingsScreen onExport onImport reminder? />`
390
+
391
+ Export/import buttons plus an optional reminder-notification switch. Deliberately just a shell: what actually gets exported/imported is entirely app-specific (your own database tables), so `onExport`/`onImport` are required — this package can't have a sensible default for "how do I export my schema". Import asks for confirmation first (via `confirmDestructive`, from `utils/helpers/`) since it replaces existing data; `onImport` itself doesn't need to.
392
+
393
+ ```tsx
394
+ import { BackupSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/BackupSettingsScreen";
395
+
396
+ <BackupSettingsScreen
397
+ onExport={shareBackup}
398
+ onImport={pickAndImportBackup}
399
+ reminder={{
400
+ enabled: reminderEnabled,
401
+ onToggle: handleToggleReminder,
402
+ intervalDays: BACKUP_REMINDER_INTERVAL_DAYS,
403
+ }}
404
+ />;
405
+ ```
406
+
407
+ Omit `reminder` entirely for an app with no backup-reminder notifications.
408
+
409
+ #### `<AboutSettingsScreen appName version description developerName />`
410
+
411
+ App name, version, one or more description paragraphs, and a "developed by" credit. `appName`/`version` are required rather than defaulted internally (keeps this package framework-agnostic) — pass `Constants.expoConfig?.name`/`.version` from the caller.
412
+
413
+ ```tsx
414
+ import { AboutSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/AboutSettingsScreen";
415
+
416
+ <AboutSettingsScreen
417
+ appName={Constants.expoConfig?.name ?? "My App"}
418
+ version={Constants.expoConfig?.version ?? "?"}
419
+ description="A personal tool to do the thing."
420
+ developerName="Your Name"
421
+ />;
422
+ ```
423
+
424
+ #### `<ContactSettingsScreen email />`
425
+
426
+ A `mailto:` button plus a short "how to report a bug" hint.
427
+
428
+ ```tsx
429
+ import { ContactSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/ContactSettingsScreen";
430
+
431
+ <ContactSettingsScreen email="you@example.com" />;
432
+ ```
433
+
434
+ #### `<PrivacySettingsScreen sections? />`
435
+
436
+ Ships with the standard "everything stays on this device" three sections (no data collected, local storage only, sharing is opt-in) as the default — the right fit for most of these small personal-data apps. Pass your own `sections` to fully replace it for an app with different data handling (e.g. one that syncs to a server).
437
+
438
+ ```tsx
439
+ import { PrivacySettingsScreen } from "@forthtilliath/react-native-kit/components/settings/PrivacySettingsScreen";
440
+
441
+ // Zero-config, uses the default copy:
442
+ <PrivacySettingsScreen />;
443
+
444
+ // Or fully custom:
445
+ <PrivacySettingsScreen
446
+ sections={[
447
+ {
448
+ title: "What we collect",
449
+ paragraphs: ["Only what you enter, synced to your account."],
450
+ },
451
+ ]}
452
+ />;
453
+ ```
454
+
455
+ For all 7 components above, styling works the same way as `ChangelogNotes`: an optional `styles` prop (all fields optional, neutral defaults), and where relevant an optional `labels` prop for the built-in French copy.
456
+
297
457
  ### Utils (`utils/`)
298
458
 
299
459
  Framework-agnostic pure functions — no React or React Native import, usable from Node/web too.
@@ -1,5 +1,6 @@
1
1
  export * from "./list";
2
2
  export * from "./picker";
3
+ export * from "./settings";
3
4
  export * from "./theme";
4
5
  export * from "./update";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from "./list";
2
2
  export * from "./picker";
3
+ export * from "./settings";
3
4
  export * from "./theme";
4
5
  export * from "./update";
@@ -0,0 +1,24 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface AboutSettingsScreenStyles {
3
+ container?: StyleProp<ViewStyle>;
4
+ appName?: StyleProp<TextStyle>;
5
+ version?: StyleProp<TextStyle>;
6
+ separator?: StyleProp<ViewStyle>;
7
+ paragraph?: StyleProp<TextStyle>;
8
+ hint?: StyleProp<TextStyle>;
9
+ }
10
+ export interface AboutSettingsScreenLabels {
11
+ version?: (version: string) => string;
12
+ developedBy?: (name: string) => string;
13
+ }
14
+ export interface AboutSettingsScreenProps {
15
+ appName: string;
16
+ version: string;
17
+ /** One or more paragraphs describing the app, rendered in order. */
18
+ description: string | string[];
19
+ developerName: string;
20
+ labels?: AboutSettingsScreenLabels;
21
+ styles?: AboutSettingsScreenStyles;
22
+ }
23
+ export declare function AboutSettingsScreen({ appName, version, description, developerName, labels, styles, }: AboutSettingsScreenProps): import("react").JSX.Element;
24
+ //# sourceMappingURL=AboutSettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AboutSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AboutSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AA8BD,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,WAAW,EACX,aAAa,EACb,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,+BA8B1B"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, View } from "react-native";
3
+ const defaultLabels = {
4
+ version: (version) => `Version ${version}`,
5
+ developedBy: (name) => `Développée par ${name}.`,
6
+ };
7
+ const defaultStyles = {
8
+ container: {},
9
+ appName: { fontSize: 22, fontWeight: "700", color: "#111827" },
10
+ version: { fontSize: 13, color: "#6b7280", marginTop: 2 },
11
+ separator: {
12
+ height: 1,
13
+ marginVertical: 16,
14
+ backgroundColor: "#e5e7eb",
15
+ },
16
+ paragraph: {
17
+ fontSize: 14,
18
+ lineHeight: 20,
19
+ color: "#111827",
20
+ marginBottom: 12,
21
+ },
22
+ hint: { fontSize: 13, color: "#6b7280" },
23
+ };
24
+ // App name, version, a short description, and a "developed by" credit — the
25
+ // standard "About" section of a settings tab. `appName`/`version` are
26
+ // required rather than defaulted from `expo-constants` so this stays
27
+ // framework-agnostic; pass `Constants.expoConfig?.name`/`.version` from the
28
+ // caller.
29
+ export function AboutSettingsScreen({ appName, version, description, developerName, labels, styles, }) {
30
+ const merged = {
31
+ container: [defaultStyles.container, styles?.container],
32
+ appName: [defaultStyles.appName, styles?.appName],
33
+ version: [defaultStyles.version, styles?.version],
34
+ separator: [defaultStyles.separator, styles?.separator],
35
+ paragraph: [defaultStyles.paragraph, styles?.paragraph],
36
+ hint: [defaultStyles.hint, styles?.hint],
37
+ };
38
+ const t = { ...defaultLabels, ...labels };
39
+ const paragraphs = Array.isArray(description) ? description : [description];
40
+ return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.appName, children: appName }), _jsx(Text, { style: merged.version, children: t.version(version) }), _jsx(View, { style: merged.separator }), paragraphs.map((paragraph) => (_jsx(Text, { style: merged.paragraph, children: paragraph }, paragraph))), _jsx(View, { style: merged.separator }), _jsx(Text, { style: merged.hint, children: t.developedBy(developerName) })] }));
41
+ }
@@ -0,0 +1,52 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface BackupSettingsScreenReminder {
3
+ enabled: boolean;
4
+ /** Set while a permission request or the toggle's own persistence is in flight. */
5
+ busy?: boolean;
6
+ onToggle: (value: boolean) => void;
7
+ /** How often the reminder fires, only used to word the hint (e.g. "every 7 days"). */
8
+ intervalDays: number;
9
+ }
10
+ export interface BackupSettingsScreenStyles {
11
+ container?: StyleProp<ViewStyle>;
12
+ hint?: StyleProp<TextStyle>;
13
+ row?: StyleProp<ViewStyle>;
14
+ button?: StyleProp<ViewStyle>;
15
+ buttonDisabled?: StyleProp<ViewStyle>;
16
+ buttonText?: StyleProp<TextStyle>;
17
+ reminderRow?: StyleProp<ViewStyle>;
18
+ reminderTextColumn?: StyleProp<ViewStyle>;
19
+ reminderLabel?: StyleProp<TextStyle>;
20
+ reminderHint?: StyleProp<TextStyle>;
21
+ activityIndicatorColor?: string;
22
+ }
23
+ export interface BackupSettingsScreenLabels {
24
+ hint?: string;
25
+ exportButton?: string;
26
+ importButton?: string;
27
+ importConfirmTitle?: string;
28
+ importConfirmMessage?: string;
29
+ importConfirmLabel?: string;
30
+ importConfirmCancelLabel?: string;
31
+ reminderLabel?: string;
32
+ reminderHint?: (intervalDays: number) => string;
33
+ exportErrorTitle?: string;
34
+ importErrorTitle?: string;
35
+ genericErrorMessage?: string;
36
+ }
37
+ export interface BackupSettingsScreenProps {
38
+ /** Exports/shares the app's data however it sees fit (e.g. a JSON file via the native share sheet). */
39
+ onExport: () => Promise<void>;
40
+ /**
41
+ * Imports data from a file the user picks, replacing what's currently
42
+ * stored. Only called after the user confirms the destructive-replace
43
+ * warning — this doesn't need to show its own confirmation.
44
+ */
45
+ onImport: () => Promise<void>;
46
+ /** Omit entirely to not show a reminder toggle at all. */
47
+ reminder?: BackupSettingsScreenReminder;
48
+ labels?: BackupSettingsScreenLabels;
49
+ styles?: BackupSettingsScreenStyles;
50
+ }
51
+ export declare function BackupSettingsScreen({ onExport, onImport, reminder, labels, styles, }: BackupSettingsScreenProps): import("react").JSX.Element;
52
+ //# sourceMappingURL=BackupSettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackupSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/BackupSettingsScreen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAYpE,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,kBAAkB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,yBAAyB;IACxC,uGAAuG;IACvG,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,4BAA4B,CAAC;IACxC,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAiED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,GACP,EAAE,yBAAyB,+BA8H3B"}
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ActivityIndicator, Alert, Pressable, Switch, Text, View, } from "react-native";
4
+ import { confirmDestructive } from "../../utils/helpers/confirmDestructive.js";
5
+ const defaultLabels = {
6
+ hint: "Les données sont stockées uniquement sur cet appareil et sont perdues en cas de réinstallation ou de mise à jour incompatible. Exporte-les régulièrement pour pouvoir les restaurer.",
7
+ exportButton: "⬆️ Exporter",
8
+ importButton: "⬇️ Importer",
9
+ importConfirmTitle: "Importer une sauvegarde ?",
10
+ importConfirmMessage: "Toutes les données actuelles seront remplacées par celles du fichier choisi.",
11
+ importConfirmLabel: "Choisir un fichier",
12
+ importConfirmCancelLabel: "Annuler",
13
+ reminderLabel: "🔔 Rappel de sauvegarde",
14
+ reminderHint: undefined,
15
+ exportErrorTitle: "Échec de l'export",
16
+ importErrorTitle: "Échec de l'import",
17
+ genericErrorMessage: "Une erreur inconnue s'est produite.",
18
+ };
19
+ const defaultStyles = {
20
+ container: {},
21
+ hint: { fontSize: 13, color: "#6b7280", marginBottom: 16 },
22
+ row: { flexDirection: "row", gap: 10 },
23
+ button: {
24
+ flex: 1,
25
+ paddingVertical: 12,
26
+ borderRadius: 8,
27
+ borderWidth: 1,
28
+ borderColor: "#2563eb",
29
+ alignItems: "center",
30
+ justifyContent: "center",
31
+ },
32
+ buttonDisabled: { opacity: 0.5 },
33
+ buttonText: { color: "#2563eb", fontWeight: "700" },
34
+ reminderRow: {
35
+ flexDirection: "row",
36
+ alignItems: "center",
37
+ gap: 12,
38
+ marginTop: 24,
39
+ },
40
+ reminderTextColumn: { flex: 1 },
41
+ reminderLabel: {
42
+ fontSize: 15,
43
+ fontWeight: "600",
44
+ color: "#111827",
45
+ marginBottom: 4,
46
+ },
47
+ reminderHint: { fontSize: 13, color: "#6b7280" },
48
+ activityIndicatorColor: undefined,
49
+ };
50
+ function errorMessage(err, fallback) {
51
+ return err instanceof Error ? err.message : fallback;
52
+ }
53
+ // Export/import buttons plus an optional reminder-notification toggle — the
54
+ // standard "Backup" section of a settings tab. Deliberately just a shell:
55
+ // what gets exported/imported is entirely app-specific (its own database
56
+ // tables), so `onExport`/`onImport` are required props, not something this
57
+ // package could have a sensible default for.
58
+ export function BackupSettingsScreen({ onExport, onImport, reminder, labels, styles, }) {
59
+ const [exporting, setExporting] = useState(false);
60
+ const [importing, setImporting] = useState(false);
61
+ const merged = {
62
+ container: [defaultStyles.container, styles?.container],
63
+ hint: [defaultStyles.hint, styles?.hint],
64
+ row: [defaultStyles.row, styles?.row],
65
+ button: [defaultStyles.button, styles?.button],
66
+ buttonDisabled: [defaultStyles.buttonDisabled, styles?.buttonDisabled],
67
+ buttonText: [defaultStyles.buttonText, styles?.buttonText],
68
+ reminderRow: [defaultStyles.reminderRow, styles?.reminderRow],
69
+ reminderTextColumn: [
70
+ defaultStyles.reminderTextColumn,
71
+ styles?.reminderTextColumn,
72
+ ],
73
+ reminderLabel: [defaultStyles.reminderLabel, styles?.reminderLabel],
74
+ reminderHint: [defaultStyles.reminderHint, styles?.reminderHint],
75
+ };
76
+ const t = { ...defaultLabels, ...labels };
77
+ const activityIndicatorColor = styles?.activityIndicatorColor ?? defaultStyles.activityIndicatorColor;
78
+ async function handleExport() {
79
+ if (exporting)
80
+ return;
81
+ setExporting(true);
82
+ try {
83
+ await onExport();
84
+ }
85
+ catch (err) {
86
+ Alert.alert(t.exportErrorTitle, errorMessage(err, t.genericErrorMessage));
87
+ }
88
+ finally {
89
+ setExporting(false);
90
+ }
91
+ }
92
+ function handleImportPress() {
93
+ if (importing)
94
+ return;
95
+ confirmDestructive(t.importConfirmTitle, () => {
96
+ void (async () => {
97
+ setImporting(true);
98
+ try {
99
+ await onImport();
100
+ }
101
+ catch (err) {
102
+ Alert.alert(t.importErrorTitle, errorMessage(err, t.genericErrorMessage));
103
+ }
104
+ finally {
105
+ setImporting(false);
106
+ }
107
+ })();
108
+ }, {
109
+ message: t.importConfirmMessage,
110
+ cancelLabel: t.importConfirmCancelLabel,
111
+ confirmLabel: t.importConfirmLabel,
112
+ });
113
+ }
114
+ return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.hint, children: t.hint }), _jsxs(View, { style: merged.row, children: [_jsx(Pressable, { style: [merged.button, exporting && merged.buttonDisabled], disabled: exporting, onPress: () => {
115
+ void handleExport();
116
+ }, accessibilityRole: "button", accessibilityLabel: t.exportButton, children: exporting ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Text, { style: merged.buttonText, children: t.exportButton })) }), _jsx(Pressable, { style: [merged.button, importing && merged.buttonDisabled], disabled: importing, onPress: handleImportPress, accessibilityRole: "button", accessibilityLabel: t.importButton, children: importing ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Text, { style: merged.buttonText, children: t.importButton })) })] }), reminder && (_jsxs(Pressable, { style: merged.reminderRow, disabled: reminder.busy, onPress: () => {
117
+ reminder.onToggle(!reminder.enabled);
118
+ }, accessibilityRole: "switch", accessibilityState: { checked: reminder.enabled }, accessibilityLabel: t.reminderLabel, children: [_jsxs(View, { style: merged.reminderTextColumn, children: [_jsx(Text, { style: merged.reminderLabel, children: t.reminderLabel }), _jsx(Text, { style: merged.reminderHint, children: t.reminderHint?.(reminder.intervalDays) ??
119
+ `Une notification tous les ${String(reminder.intervalDays)} jours pour penser à exporter.` })] }), reminder.busy ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Switch, { value: reminder.enabled, onValueChange: reminder.onToggle }))] }))] }));
120
+ }
@@ -0,0 +1,19 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface ContactSettingsScreenStyles {
3
+ container?: StyleProp<ViewStyle>;
4
+ hint?: StyleProp<TextStyle>;
5
+ emailButton?: StyleProp<ViewStyle>;
6
+ emailButtonText?: StyleProp<TextStyle>;
7
+ separator?: StyleProp<ViewStyle>;
8
+ }
9
+ export interface ContactSettingsScreenLabels {
10
+ hint?: string;
11
+ footer?: string;
12
+ }
13
+ export interface ContactSettingsScreenProps {
14
+ email: string;
15
+ labels?: ContactSettingsScreenLabels;
16
+ styles?: ContactSettingsScreenStyles;
17
+ }
18
+ export declare function ContactSettingsScreen({ email, labels, styles, }: ContactSettingsScreenProps): import("react").JSX.Element;
19
+ //# sourceMappingURL=ContactSettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ContactSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,2BAA2B,CAAC;IACrC,MAAM,CAAC,EAAE,2BAA2B,CAAC;CACtC;AA6BD,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,MAAM,EACN,MAAM,GACP,EAAE,0BAA0B,+BA8B5B"}
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Linking, Pressable, Text, View } from "react-native";
3
+ const defaultLabels = {
4
+ hint: "Une question, un bug, une suggestion ?",
5
+ footer: "Si tu remontes un bug, précise si possible ce que tu faisais et ce que tu attendais — ça aide à le reproduire.",
6
+ };
7
+ const defaultStyles = {
8
+ container: {},
9
+ hint: { fontSize: 13, color: "#6b7280", marginBottom: 12 },
10
+ emailButton: {
11
+ paddingVertical: 12,
12
+ paddingHorizontal: 16,
13
+ borderRadius: 8,
14
+ borderWidth: 1,
15
+ borderColor: "#2563eb",
16
+ alignItems: "center",
17
+ },
18
+ emailButtonText: { color: "#2563eb", fontWeight: "700" },
19
+ separator: {
20
+ height: 1,
21
+ marginVertical: 16,
22
+ backgroundColor: "#e5e7eb",
23
+ },
24
+ };
25
+ // A mailto: button plus a short "how to report a bug" hint — the standard
26
+ // "Contact" section of a settings tab.
27
+ export function ContactSettingsScreen({ email, labels, styles, }) {
28
+ const merged = {
29
+ container: [defaultStyles.container, styles?.container],
30
+ hint: [defaultStyles.hint, styles?.hint],
31
+ emailButton: [defaultStyles.emailButton, styles?.emailButton],
32
+ emailButtonText: [defaultStyles.emailButtonText, styles?.emailButtonText],
33
+ separator: [defaultStyles.separator, styles?.separator],
34
+ };
35
+ const t = { ...defaultLabels, ...labels };
36
+ return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.hint, children: t.hint }), _jsx(Pressable, { style: merged.emailButton, onPress: () => {
37
+ void Linking.openURL(`mailto:${email}`);
38
+ }, accessibilityRole: "button", accessibilityLabel: `Envoyer un email à ${email}`, children: _jsx(Text, { style: merged.emailButtonText, children: `✉️ ${email}` }) }), _jsx(View, { style: merged.separator }), _jsx(Text, { style: merged.hint, children: t.footer })] }));
39
+ }
@@ -0,0 +1,23 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface PrivacySettingsScreenSection {
3
+ title: string;
4
+ paragraphs: string[];
5
+ }
6
+ export interface PrivacySettingsScreenStyles {
7
+ container?: StyleProp<ViewStyle>;
8
+ title?: StyleProp<TextStyle>;
9
+ paragraph?: StyleProp<TextStyle>;
10
+ separator?: StyleProp<ViewStyle>;
11
+ }
12
+ export interface PrivacySettingsScreenProps {
13
+ /**
14
+ * Defaults to the standard "everything stays on this device" three
15
+ * sections (no data collected, local storage only, sharing is opt-in).
16
+ * Pass your own to describe a different setup (e.g. an app that does sync
17
+ * to a server) — this fully replaces the default, it isn't merged with it.
18
+ */
19
+ sections?: PrivacySettingsScreenSection[];
20
+ styles?: PrivacySettingsScreenStyles;
21
+ }
22
+ export declare function PrivacySettingsScreen({ sections, styles, }: PrivacySettingsScreenProps): import("react").JSX.Element;
23
+ //# sourceMappingURL=PrivacySettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrivacySettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/PrivacySettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,4BAA4B,EAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,2BAA2B,CAAC;CACtC;AA4CD,wBAAgB,qBAAqB,CAAC,EACpC,QAA0B,EAC1B,MAAM,GACP,EAAE,0BAA0B,+BAuB5B"}
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, View } from "react-native";
3
+ const defaultSections = [
4
+ {
5
+ title: "Aucune donnée envoyée nulle part",
6
+ paragraphs: [
7
+ "Cette application ne collecte aucune donnée, ne fait appel à aucun serveur, et ne contient aucun outil de suivi ni de publicité.",
8
+ ],
9
+ },
10
+ {
11
+ title: "Stockage local uniquement",
12
+ paragraphs: [
13
+ "Tout ce que tu saisis reste stocké uniquement sur cet appareil, dans le stockage local de l'application. Rien n'est envoyé ailleurs.",
14
+ "Ces données sont perdues si tu désinstalles l'application ou si tu effaces son stockage — pense à utiliser une sauvegarde régulière pour pouvoir les restaurer.",
15
+ ],
16
+ },
17
+ {
18
+ title: "Partage volontaire uniquement",
19
+ paragraphs: [
20
+ "La seule façon pour une donnée de quitter cet appareil, c'est quand tu choisis toi-même de la partager (export d'une sauvegarde...), via le sélecteur de partage natif du téléphone.",
21
+ ],
22
+ },
23
+ ];
24
+ const defaultStyles = {
25
+ container: {},
26
+ title: { fontSize: 15, fontWeight: "700", color: "#111827", marginBottom: 6 },
27
+ paragraph: {
28
+ fontSize: 14,
29
+ lineHeight: 20,
30
+ color: "#374151",
31
+ marginBottom: 8,
32
+ },
33
+ separator: {
34
+ height: 1,
35
+ marginVertical: 16,
36
+ backgroundColor: "#e5e7eb",
37
+ },
38
+ };
39
+ // Privacy section of a settings tab: a list of titled paragraph sections,
40
+ // separated by a thin rule. Ships with the standard "everything stays local"
41
+ // copy as a default — override `sections` entirely for an app with different
42
+ // data handling (e.g. one that does sync to a server).
43
+ export function PrivacySettingsScreen({ sections = defaultSections, styles, }) {
44
+ const merged = {
45
+ container: [defaultStyles.container, styles?.container],
46
+ title: [defaultStyles.title, styles?.title],
47
+ paragraph: [defaultStyles.paragraph, styles?.paragraph],
48
+ separator: [defaultStyles.separator, styles?.separator],
49
+ };
50
+ return (_jsx(View, { style: merged.container, children: sections.map((section, index) => (_jsxs(View, { children: [index > 0 && _jsx(View, { style: merged.separator }), _jsx(Text, { style: merged.title, children: section.title }), section.paragraphs.map((paragraph) => (_jsx(Text, { style: merged.paragraph, children: paragraph }, paragraph)))] }, section.title))) }));
51
+ }
@@ -0,0 +1,24 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface SettingsMenuItem {
3
+ /** Stable React key — also what distinguishes otherwise-identical items. */
4
+ key: string;
5
+ emoji: string;
6
+ title: string;
7
+ hint?: string;
8
+ onPress: () => void;
9
+ }
10
+ export interface SettingsMenuStyles {
11
+ container?: StyleProp<ViewStyle>;
12
+ row?: StyleProp<ViewStyle>;
13
+ titleColumn?: StyleProp<ViewStyle>;
14
+ emoji?: StyleProp<TextStyle>;
15
+ title?: StyleProp<TextStyle>;
16
+ hint?: StyleProp<TextStyle>;
17
+ chevronColor?: string;
18
+ }
19
+ export interface SettingsMenuProps {
20
+ items: SettingsMenuItem[];
21
+ styles?: SettingsMenuStyles;
22
+ }
23
+ export declare function SettingsMenu({ items, styles }: SettingsMenuProps): import("react").JSX.Element;
24
+ //# sourceMappingURL=SettingsMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsMenu.d.ts","sourceRoot":"","sources":["../../../src/components/settings/SettingsMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AA4BD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,iBAAiB,+BA+BhE"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Pressable, Text, View } from "react-native";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+ const defaultStyles = {
5
+ container: { gap: 10 },
6
+ row: {
7
+ flexDirection: "row",
8
+ alignItems: "center",
9
+ gap: 12,
10
+ borderWidth: 1,
11
+ borderColor: "#e5e7eb",
12
+ borderRadius: 10,
13
+ padding: 14,
14
+ },
15
+ titleColumn: { flex: 1 },
16
+ emoji: { fontSize: 24 },
17
+ title: {
18
+ fontSize: 16,
19
+ fontWeight: "600",
20
+ color: "#111827",
21
+ },
22
+ hint: { fontSize: 12, color: "#6b7280", marginTop: 2 },
23
+ chevronColor: "#9ca3af",
24
+ };
25
+ // Menu screen for a settings tab: one row per section (emoji + title + hint),
26
+ // navigating to it on press. Has no opinion on how navigation works — each
27
+ // item carries its own `onPress` (e.g. `() => router.push("/settings/xxx")`),
28
+ // so this doesn't depend on expo-router or any other specific navigator.
29
+ export function SettingsMenu({ items, styles }) {
30
+ const merged = {
31
+ container: [defaultStyles.container, styles?.container],
32
+ row: [defaultStyles.row, styles?.row],
33
+ titleColumn: [defaultStyles.titleColumn, styles?.titleColumn],
34
+ emoji: [defaultStyles.emoji, styles?.emoji],
35
+ title: [defaultStyles.title, styles?.title],
36
+ hint: [defaultStyles.hint, styles?.hint],
37
+ };
38
+ const chevronColor = styles?.chevronColor ?? defaultStyles.chevronColor;
39
+ return (_jsx(View, { style: merged.container, children: items.map((item) => (_jsxs(Pressable, { style: merged.row, onPress: item.onPress, accessibilityRole: "button", accessibilityLabel: item.title, children: [_jsx(Text, { style: merged.emoji, children: item.emoji }), _jsxs(View, { style: merged.titleColumn, children: [_jsx(Text, { style: merged.title, children: item.title }), item.hint ? _jsx(Text, { style: merged.hint, children: item.hint }) : null] }), _jsx(Ionicons, { name: "chevron-forward", size: 20, color: chevronColor })] }, item.key))) }));
40
+ }
@@ -0,0 +1,32 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ import type { ThemePreference } from "../../hooks/useEffectiveColorScheme.js";
3
+ export interface ThemeSettingsScreenEmojis {
4
+ light?: string;
5
+ dark?: string;
6
+ system?: string;
7
+ }
8
+ export interface ThemeSettingsScreenLabels {
9
+ light?: string;
10
+ dark?: string;
11
+ system?: string;
12
+ hint?: string;
13
+ }
14
+ export interface ThemeSettingsScreenStyles {
15
+ container?: StyleProp<ViewStyle>;
16
+ hint?: StyleProp<TextStyle>;
17
+ row?: StyleProp<ViewStyle>;
18
+ rowActive?: StyleProp<ViewStyle>;
19
+ emoji?: StyleProp<TextStyle>;
20
+ label?: StyleProp<TextStyle>;
21
+ labelActive?: StyleProp<TextStyle>;
22
+ check?: StyleProp<TextStyle>;
23
+ }
24
+ export interface ThemeSettingsScreenProps {
25
+ value: ThemePreference;
26
+ onChange: (preference: ThemePreference) => void;
27
+ emojis?: ThemeSettingsScreenEmojis;
28
+ labels?: ThemeSettingsScreenLabels;
29
+ styles?: ThemeSettingsScreenStyles;
30
+ }
31
+ export declare function ThemeSettingsScreen({ value, onChange, emojis, labels, styles, }: ThemeSettingsScreenProps): import("react").JSX.Element;
32
+ //# sourceMappingURL=ThemeSettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ThemeSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE9E,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AAoDD,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,+BA2C1B"}
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Pressable, Text, View } from "react-native";
3
+ const OPTIONS = ["light", "dark", "system"];
4
+ const defaultEmojis = {
5
+ light: "☀️",
6
+ dark: "🌙",
7
+ system: "⚙️",
8
+ };
9
+ const defaultLabels = {
10
+ light: "Clair",
11
+ dark: "Sombre",
12
+ system: "Système",
13
+ hint: "« Système » suit automatiquement le réglage clair/sombre de ton téléphone.",
14
+ };
15
+ const defaultStyles = {
16
+ container: { gap: 10 },
17
+ hint: { fontSize: 13, color: "#6b7280", marginBottom: 6 },
18
+ row: {
19
+ flexDirection: "row",
20
+ alignItems: "center",
21
+ gap: 12,
22
+ borderWidth: 1,
23
+ borderColor: "#e5e7eb",
24
+ borderRadius: 10,
25
+ padding: 14,
26
+ },
27
+ rowActive: {
28
+ borderColor: "#2563eb",
29
+ backgroundColor: "#2563eb1a",
30
+ },
31
+ emoji: { fontSize: 20 },
32
+ label: {
33
+ flex: 1,
34
+ fontSize: 15,
35
+ fontWeight: "600",
36
+ color: "#111827",
37
+ },
38
+ labelActive: { color: "#2563eb" },
39
+ check: {
40
+ fontSize: 16,
41
+ fontWeight: "700",
42
+ color: "#2563eb",
43
+ },
44
+ };
45
+ // Selectable list of rows (emoji + label + checkmark) for a light/dark/system
46
+ // theme preference — same data contract as `ThemeOptionList`, but an emoji +
47
+ // plain-text checkmark instead of vector icons. Pick whichever visual fits
48
+ // your app; both cover the exact same `light`/`dark`/`system` preference.
49
+ export function ThemeSettingsScreen({ value, onChange, emojis, labels, styles, }) {
50
+ // Style fields are merged as arrays (default, then override) so a partial
51
+ // override only changes the properties it specifies instead of replacing
52
+ // the whole default style object.
53
+ const merged = {
54
+ container: [defaultStyles.container, styles?.container],
55
+ hint: [defaultStyles.hint, styles?.hint],
56
+ row: [defaultStyles.row, styles?.row],
57
+ rowActive: [defaultStyles.rowActive, styles?.rowActive],
58
+ emoji: [defaultStyles.emoji, styles?.emoji],
59
+ label: [defaultStyles.label, styles?.label],
60
+ labelActive: [defaultStyles.labelActive, styles?.labelActive],
61
+ check: [defaultStyles.check, styles?.check],
62
+ };
63
+ const e = { ...defaultEmojis, ...emojis };
64
+ const t = { ...defaultLabels, ...labels };
65
+ return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.hint, children: t.hint }), OPTIONS.map((option) => {
66
+ const active = option === value;
67
+ return (_jsxs(Pressable, { style: [merged.row, active && merged.rowActive], onPress: () => {
68
+ onChange(option);
69
+ }, accessibilityRole: "radio", accessibilityState: { checked: active }, accessibilityLabel: t[option], children: [_jsx(Text, { style: merged.emoji, children: e[option] }), _jsx(Text, { style: [merged.label, active && merged.labelActive], children: t[option] }), active && _jsx(Text, { style: merged.check, children: "\u2713" })] }, option));
70
+ })] }));
71
+ }
@@ -0,0 +1,62 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ import type { ChangelogNotesStyles } from "../update/ChangelogNotes.js";
3
+ export interface UpdateSettingsScreenRelease {
4
+ version: string;
5
+ notes: string;
6
+ apkUrl: string;
7
+ }
8
+ export interface UpdateSettingsScreenHistoryEntry {
9
+ version: string;
10
+ notes: string;
11
+ publishedAt?: string | null;
12
+ }
13
+ export interface UpdateSettingsScreenLabels {
14
+ installedVersionLabel?: string;
15
+ checkButton?: string;
16
+ upToDate?: string;
17
+ checkError?: string;
18
+ downloadError?: string;
19
+ availableTitle?: (version: string) => string;
20
+ installButton?: (version: string) => string;
21
+ installButtonAccessibilityLabel?: (version: string) => string;
22
+ downloadingLabel?: (percent: number) => string;
23
+ downloadingHint?: string;
24
+ historyTitle?: string;
25
+ }
26
+ export interface UpdateSettingsScreenStyles {
27
+ container?: StyleProp<ViewStyle>;
28
+ infoBox?: StyleProp<ViewStyle>;
29
+ infoLabel?: StyleProp<TextStyle>;
30
+ infoValue?: StyleProp<TextStyle>;
31
+ helpText?: StyleProp<TextStyle>;
32
+ errorText?: StyleProp<TextStyle>;
33
+ button?: StyleProp<ViewStyle>;
34
+ buttonDisabled?: StyleProp<ViewStyle>;
35
+ buttonText?: StyleProp<TextStyle>;
36
+ activityIndicatorColor?: string;
37
+ updateAvailableBox?: StyleProp<ViewStyle>;
38
+ updateAvailableTitle?: StyleProp<TextStyle>;
39
+ changelog?: StyleProp<ViewStyle>;
40
+ changelogTitle?: StyleProp<TextStyle>;
41
+ changelogEntry?: StyleProp<ViewStyle>;
42
+ changelogEntryHeader?: StyleProp<ViewStyle>;
43
+ changelogVersion?: StyleProp<TextStyle>;
44
+ changelogDate?: StyleProp<TextStyle>;
45
+ notes?: ChangelogNotesStyles;
46
+ }
47
+ export interface UpdateSettingsScreenProps {
48
+ currentVersion: string;
49
+ /** Fetches the latest available release, or `null` if there is none. */
50
+ checkForUpdate: () => Promise<UpdateSettingsScreenRelease | null>;
51
+ /** Positive when `a` is newer than `b`, e.g. semver comparison. */
52
+ compareVersions: (a: string, b: string) => number;
53
+ downloadAndInstallApk: (apkUrl: string, onProgress?: (fraction: number) => void) => Promise<void>;
54
+ /** Omit to not show a version history section at all. */
55
+ fetchReleaseHistory?: () => Promise<UpdateSettingsScreenHistoryEntry[]>;
56
+ /** Locale for formatting each history entry's date. Defaults to `"fr-FR"`. */
57
+ dateLocale?: string;
58
+ labels?: UpdateSettingsScreenLabels;
59
+ styles?: UpdateSettingsScreenStyles;
60
+ }
61
+ export declare function UpdateSettingsScreen({ currentVersion, checkForUpdate, compareVersions, downloadAndInstallApk, fetchReleaseHistory, dateLocale, labels, styles, }: UpdateSettingsScreenProps): import("react").JSX.Element;
62
+ //# sourceMappingURL=UpdateSettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpdateSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/UpdateSettingsScreen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGxE,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAUD,MAAM,WAAW,0BAA0B;IACzC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,+BAA+B,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5C,gBAAgB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,oBAAoB,CAAC;CAC9B;AAED,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,cAAc,EAAE,MAAM,OAAO,CAAC,2BAA2B,GAAG,IAAI,CAAC,CAAC;IAClE,mEAAmE;IACnE,eAAe,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,qBAAqB,EAAE,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KACpC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,gCAAgC,EAAE,CAAC,CAAC;IACxE,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AA2FD,wBAAgB,oBAAoB,CAAC,EACnC,cAAc,EACd,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,UAAoB,EACpB,MAAM,EACN,MAAM,GACP,EAAE,yBAAyB,+BA+L3B"}
@@ -0,0 +1,178 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { ActivityIndicator, Pressable, Text, View } from "react-native";
4
+ import { ChangelogNotes } from "../update/ChangelogNotes.js";
5
+ const defaultLabels = {
6
+ installedVersionLabel: "Version installée",
7
+ checkButton: "Rechercher une mise à jour",
8
+ upToDate: "Tu as déjà la dernière version.",
9
+ checkError: "Impossible de vérifier les mises à jour.",
10
+ downloadError: "Le téléchargement a échoué.",
11
+ availableTitle: (version) => `Version ${version} disponible`,
12
+ installButton: () => "Télécharger et installer",
13
+ installButtonAccessibilityLabel: (version) => `Télécharger et installer la version ${version}`,
14
+ downloadingLabel: (percent) => `Téléchargement… ${String(percent)}%`,
15
+ downloadingHint: "Ton téléphone va ensuite te demander confirmation pour installer la mise à jour.",
16
+ historyTitle: "Historique des versions",
17
+ };
18
+ const defaultStyles = {
19
+ container: { gap: 4 },
20
+ infoBox: {
21
+ backgroundColor: "#ffffff",
22
+ borderWidth: 1,
23
+ borderColor: "#e5e7eb",
24
+ borderRadius: 10,
25
+ padding: 14,
26
+ marginBottom: 8,
27
+ },
28
+ infoLabel: { fontSize: 13, fontWeight: "600", color: "#6b7280" },
29
+ infoValue: {
30
+ fontSize: 18,
31
+ fontWeight: "700",
32
+ color: "#111827",
33
+ marginTop: 2,
34
+ },
35
+ helpText: { fontSize: 12, color: "#6b7280", marginTop: 8 },
36
+ errorText: { fontSize: 12, color: "#dc2626", marginTop: 8 },
37
+ button: {
38
+ backgroundColor: "#2563eb",
39
+ borderRadius: 10,
40
+ paddingVertical: 14,
41
+ alignItems: "center",
42
+ marginTop: 8,
43
+ },
44
+ buttonDisabled: { opacity: 0.6 },
45
+ buttonText: { color: "#ffffff", fontSize: 16, fontWeight: "700" },
46
+ activityIndicatorColor: "#ffffff",
47
+ updateAvailableBox: {
48
+ backgroundColor: "#ffffff",
49
+ borderWidth: 1,
50
+ borderColor: "#e5e7eb",
51
+ borderRadius: 10,
52
+ padding: 14,
53
+ marginTop: 12,
54
+ gap: 4,
55
+ },
56
+ updateAvailableTitle: { fontSize: 15, fontWeight: "700", color: "#111827" },
57
+ changelog: { marginTop: 28 },
58
+ changelogTitle: {
59
+ fontSize: 15,
60
+ fontWeight: "700",
61
+ color: "#111827",
62
+ marginBottom: 8,
63
+ },
64
+ changelogEntry: {
65
+ backgroundColor: "#ffffff",
66
+ borderWidth: 1,
67
+ borderColor: "#e5e7eb",
68
+ borderRadius: 10,
69
+ padding: 14,
70
+ marginBottom: 8,
71
+ },
72
+ changelogEntryHeader: {
73
+ flexDirection: "row",
74
+ justifyContent: "space-between",
75
+ alignItems: "center",
76
+ },
77
+ changelogVersion: { fontSize: 14, fontWeight: "700", color: "#111827" },
78
+ changelogDate: { fontSize: 12, color: "#6b7280" },
79
+ notes: undefined,
80
+ };
81
+ // Full "check for update" screen: installed version, a manual check button,
82
+ // the available-update box (with changelog + install button) or an
83
+ // up-to-date/error message, download progress, and — if
84
+ // `fetchReleaseHistory` is passed — a list of past releases. Checks
85
+ // automatically once on mount (the caller decided to navigate here, no need
86
+ // to make them tap "check" again).
87
+ export function UpdateSettingsScreen({ currentVersion, checkForUpdate, compareVersions, downloadAndInstallApk, fetchReleaseHistory, dateLocale = "fr-FR", labels, styles, }) {
88
+ const merged = {
89
+ container: [defaultStyles.container, styles?.container],
90
+ infoBox: [defaultStyles.infoBox, styles?.infoBox],
91
+ infoLabel: [defaultStyles.infoLabel, styles?.infoLabel],
92
+ infoValue: [defaultStyles.infoValue, styles?.infoValue],
93
+ helpText: [defaultStyles.helpText, styles?.helpText],
94
+ errorText: [defaultStyles.errorText, styles?.errorText],
95
+ button: [defaultStyles.button, styles?.button],
96
+ buttonDisabled: [defaultStyles.buttonDisabled, styles?.buttonDisabled],
97
+ buttonText: [defaultStyles.buttonText, styles?.buttonText],
98
+ updateAvailableBox: [
99
+ defaultStyles.updateAvailableBox,
100
+ styles?.updateAvailableBox,
101
+ ],
102
+ updateAvailableTitle: [
103
+ defaultStyles.updateAvailableTitle,
104
+ styles?.updateAvailableTitle,
105
+ ],
106
+ changelog: [defaultStyles.changelog, styles?.changelog],
107
+ changelogTitle: [defaultStyles.changelogTitle, styles?.changelogTitle],
108
+ changelogEntry: [defaultStyles.changelogEntry, styles?.changelogEntry],
109
+ changelogEntryHeader: [
110
+ defaultStyles.changelogEntryHeader,
111
+ styles?.changelogEntryHeader,
112
+ ],
113
+ changelogVersion: [
114
+ defaultStyles.changelogVersion,
115
+ styles?.changelogVersion,
116
+ ],
117
+ changelogDate: [defaultStyles.changelogDate, styles?.changelogDate],
118
+ notes: styles?.notes ?? defaultStyles.notes,
119
+ };
120
+ const activityIndicatorColor = styles?.activityIndicatorColor ?? defaultStyles.activityIndicatorColor;
121
+ const t = { ...defaultLabels, ...labels };
122
+ const [updateState, setUpdateState] = useState({
123
+ status: "idle",
124
+ });
125
+ const [releaseHistory, setReleaseHistory] = useState(null);
126
+ useEffect(() => {
127
+ if (!fetchReleaseHistory)
128
+ return;
129
+ fetchReleaseHistory()
130
+ .then(setReleaseHistory)
131
+ .catch(() => {
132
+ setReleaseHistory([]);
133
+ });
134
+ }, [fetchReleaseHistory]);
135
+ const handleCheckForUpdate = useCallback(async () => {
136
+ setUpdateState({ status: "checking" });
137
+ try {
138
+ const release = await checkForUpdate();
139
+ if (!release || compareVersions(release.version, currentVersion) <= 0) {
140
+ setUpdateState({ status: "up-to-date" });
141
+ return;
142
+ }
143
+ setUpdateState({ status: "available", release });
144
+ }
145
+ catch {
146
+ setUpdateState({ status: "error", message: t.checkError });
147
+ }
148
+ }, [checkForUpdate, compareVersions, currentVersion, t.checkError]);
149
+ useEffect(() => {
150
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- the caller navigated here specifically to check, so this fires a real check on mount rather than waiting for a manual tap; the actual state update happens after the async checkForUpdate() call, not synchronously.
151
+ void handleCheckForUpdate();
152
+ // eslint-disable-next-line react-hooks/exhaustive-deps, @eslint-react/exhaustive-deps -- runs once on mount by design; handleCheckForUpdate is expected to be stable enough for the screen's lifetime.
153
+ }, []);
154
+ async function handleInstall(apkUrl) {
155
+ setUpdateState({ status: "downloading", progress: 0 });
156
+ try {
157
+ await downloadAndInstallApk(apkUrl, (progress) => {
158
+ setUpdateState({ status: "downloading", progress });
159
+ });
160
+ setUpdateState({ status: "idle" });
161
+ }
162
+ catch {
163
+ setUpdateState({ status: "error", message: t.downloadError });
164
+ }
165
+ }
166
+ const isBusy = updateState.status === "checking" || updateState.status === "downloading";
167
+ const notesStyles = useMemo(() => ({
168
+ heading: merged.updateAvailableTitle,
169
+ text: merged.helpText,
170
+ ...merged.notes,
171
+ }), [merged.helpText, merged.notes, merged.updateAvailableTitle]);
172
+ return (_jsxs(View, { style: merged.container, children: [_jsxs(View, { style: merged.infoBox, children: [_jsx(Text, { style: merged.infoLabel, children: t.installedVersionLabel }), _jsx(Text, { style: merged.infoValue, children: currentVersion })] }), updateState.status !== "available" &&
173
+ updateState.status !== "downloading" && (_jsx(Pressable, { style: [merged.button, isBusy && merged.buttonDisabled], onPress: () => {
174
+ void handleCheckForUpdate();
175
+ }, disabled: isBusy, accessibilityRole: "button", accessibilityLabel: t.checkButton, children: updateState.status === "checking" ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Text, { style: merged.buttonText, children: t.checkButton })) })), updateState.status === "up-to-date" && (_jsx(Text, { style: merged.helpText, children: t.upToDate })), updateState.status === "error" && (_jsx(Text, { style: merged.errorText, children: updateState.message })), updateState.status === "available" && (_jsxs(View, { style: merged.updateAvailableBox, children: [_jsx(Text, { style: merged.updateAvailableTitle, children: t.availableTitle(updateState.release.version) }), updateState.release.notes ? (_jsx(ChangelogNotes, { notes: updateState.release.notes, styles: merged.notes })) : null, _jsx(Pressable, { style: merged.button, onPress: () => {
176
+ void handleInstall(updateState.release.apkUrl);
177
+ }, accessibilityRole: "button", accessibilityLabel: t.installButtonAccessibilityLabel(updateState.release.version), children: _jsx(Text, { style: merged.buttonText, children: t.installButton(updateState.release.version) }) })] })), updateState.status === "downloading" && (_jsxs(View, { style: merged.updateAvailableBox, children: [_jsx(Text, { style: merged.helpText, children: t.downloadingLabel(Math.round(updateState.progress * 100)) }), _jsx(Text, { style: merged.helpText, children: t.downloadingHint })] })), releaseHistory && releaseHistory.length > 0 && (_jsxs(View, { style: merged.changelog, children: [_jsx(Text, { style: merged.changelogTitle, children: t.historyTitle }), releaseHistory.map((release) => (_jsxs(View, { style: merged.changelogEntry, children: [_jsxs(View, { style: merged.changelogEntryHeader, children: [_jsxs(Text, { style: merged.changelogVersion, children: ["v", release.version] }), release.publishedAt ? (_jsx(Text, { style: merged.changelogDate, children: new Date(release.publishedAt).toLocaleDateString(dateLocale) })) : null] }), release.notes ? (_jsx(ChangelogNotes, { notes: release.notes, styles: notesStyles })) : null] }, release.version)))] }))] }));
178
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./AboutSettingsScreen";
2
+ export * from "./BackupSettingsScreen";
3
+ export * from "./ContactSettingsScreen";
4
+ export * from "./PrivacySettingsScreen";
5
+ export * from "./SettingsMenu";
6
+ export * from "./ThemeSettingsScreen";
7
+ export * from "./UpdateSettingsScreen";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/settings/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from "./AboutSettingsScreen";
2
+ export * from "./BackupSettingsScreen";
3
+ export * from "./ContactSettingsScreen";
4
+ export * from "./PrivacySettingsScreen";
5
+ export * from "./SettingsMenu";
6
+ export * from "./ThemeSettingsScreen";
7
+ export * from "./UpdateSettingsScreen";
package/dist/index.d.ts CHANGED
@@ -3,6 +3,13 @@ export * from "./components/list/Thumbnail.js";
3
3
  export * from "./components/picker/PhotoPicker.js";
4
4
  export * from "./components/picker/PickerModal.js";
5
5
  export * from "./components/picker/VoiceSearchButton.js";
6
+ export * from "./components/settings/AboutSettingsScreen.js";
7
+ export * from "./components/settings/BackupSettingsScreen.js";
8
+ export * from "./components/settings/ContactSettingsScreen.js";
9
+ export * from "./components/settings/PrivacySettingsScreen.js";
10
+ export * from "./components/settings/SettingsMenu.js";
11
+ export * from "./components/settings/ThemeSettingsScreen.js";
12
+ export * from "./components/settings/UpdateSettingsScreen.js";
6
13
  export * from "./components/theme/ThemeOptionList.js";
7
14
  export * from "./components/theme/ThemeToggle.js";
8
15
  export * from "./components/update/ChangelogNotes.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oCAAoC,CAAC;AACnD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uCAAuC,CAAC;AACtD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oCAAoC,CAAC;AACnD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC"}
package/dist/index.js CHANGED
@@ -8,6 +8,13 @@ export * from "./components/list/Thumbnail.js";
8
8
  export * from "./components/picker/PhotoPicker.js";
9
9
  export * from "./components/picker/PickerModal.js";
10
10
  export * from "./components/picker/VoiceSearchButton.js";
11
+ export * from "./components/settings/AboutSettingsScreen.js";
12
+ export * from "./components/settings/BackupSettingsScreen.js";
13
+ export * from "./components/settings/ContactSettingsScreen.js";
14
+ export * from "./components/settings/PrivacySettingsScreen.js";
15
+ export * from "./components/settings/SettingsMenu.js";
16
+ export * from "./components/settings/ThemeSettingsScreen.js";
17
+ export * from "./components/settings/UpdateSettingsScreen.js";
11
18
  export * from "./components/theme/ThemeOptionList.js";
12
19
  export * from "./components/theme/ThemeToggle.js";
13
20
  export * from "./components/update/ChangelogNotes.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forthtilliath/react-native-kit",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -32,20 +32,20 @@
32
32
  "react-native-gesture-handler": ">=2.20.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@expo/vector-icons": "^15.0.2",
36
- "@types/react": "^19.2.17",
35
+ "@expo/vector-icons": "^15.1.1",
36
+ "@types/react": "^19.2.18",
37
37
  "@types/react-test-renderer": "^19.1.0",
38
38
  "eslint": "10.7.0",
39
- "expo-image-picker": "~57.0.7",
39
+ "expo-image-picker": "~57.0.9",
40
40
  "expo-speech-recognition": "^56.0.1",
41
- "react": "^19.2.7",
41
+ "react": "^19.2.8",
42
42
  "react-native": "0.86.0",
43
43
  "react-native-gesture-handler": "~2.32.0",
44
44
  "react-test-renderer": "19.2.3",
45
45
  "typescript": "^5.9.3",
46
46
  "vitest": "^4.1.10",
47
- "@forthtilliath/eslint-config": "0.0.0",
48
- "@forthtilliath/typescript-config": "0.0.0"
47
+ "@forthtilliath/typescript-config": "0.0.0",
48
+ "@forthtilliath/eslint-config": "0.0.0"
49
49
  },
50
50
  "scripts": {
51
51
  "lint": "eslint . --max-warnings 0",