From: Thibaud Moustier Date: Wed, 25 Feb 2026 15:30:44 +0000 (+0100) Subject: Mobile : creation fenetre Wallet X-Git-Url: https://git.digitality.be/?a=commitdiff_plain;h=157d4e105d01302d3f0c2d50f930d9d1e33290f1;p=pdw25-26 Mobile : creation fenetre Wallet --- diff --git a/Wallette/mobile/App.tsx b/Wallette/mobile/App.tsx index c1f7b6f..d662a45 100644 --- a/Wallette/mobile/App.tsx +++ b/Wallette/mobile/App.tsx @@ -8,6 +8,7 @@ import SettingsScreen from "./src/screens/SettingsScreen"; import HistoryScreen from "./src/screens/HistoryScreen"; import AlertsScreen from "./src/screens/AlertsScreen"; import StrategyScreen from "./src/screens/StrategyScreen"; +import WalletScreen from "./src/screens/WalletScreen"; export type RootStackParamList = { Dashboard: undefined; @@ -15,6 +16,7 @@ export type RootStackParamList = { History: undefined; Alerts: undefined; Strategy: undefined; + Wallet: undefined; }; const Stack = createNativeStackNavigator(); @@ -28,7 +30,6 @@ export default function App() { component={DashboardScreen} options={({ navigation }) => ({ title: "Dashboard", - // ✅ icônes header à droite headerRight: () => ( navigation.navigate("Alerts")}> @@ -47,29 +48,11 @@ export default function App() { })} /> - - - - - - - + + + + + ); diff --git a/Wallette/mobile/src/models/Wallet.ts b/Wallette/mobile/src/models/Wallet.ts new file mode 100644 index 0000000..f36769f --- /dev/null +++ b/Wallette/mobile/src/models/Wallet.ts @@ -0,0 +1,11 @@ +/** + * Wallet (Step 1) + * --------------- + * Mono-utilisateur, mono-crypto : on stocke seulement BTC. + * (Step 3 : on passera à plusieurs cryptos + valeur globale) + */ +export interface WalletState { + assetSymbol: "BTC"; + quantity: number; // ex: 0.25 + updatedAtMs: number; +} \ No newline at end of file diff --git a/Wallette/mobile/src/screens/DashboardScreen.tsx b/Wallette/mobile/src/screens/DashboardScreen.tsx index 61905dd..1efa648 100644 --- a/Wallette/mobile/src/screens/DashboardScreen.tsx +++ b/Wallette/mobile/src/screens/DashboardScreen.tsx @@ -23,12 +23,24 @@ import type { Alert } from "../types/Alert"; import { alertStore } from "../services/alertStore"; import { showAlertNotification } from "../services/notificationService"; +import { loadWallet } from "../utils/walletStorage"; +import type { WalletState } from "../models/Wallet"; + /** - * DashboardScreen (WF-01) — Responsive - * ----------------------------------- - * Objectif : coller au mockup, éviter le scroll. - * - On enlève le gros bloc "Actions" (navigation via header icons) - * - Certaines cartes deviennent cliquables (tap-to-open) + * DashboardScreen (WF-01) — Responsive + No-scroll goal + * ---------------------------------------------------- + * - Navigation via header icons (Alerts / History / Settings) dans App.tsx + * - Cartes cliquables : + * * Portefeuille -> Wallet + * * Urgence -> Alertes + * * Prix BTC -> Historique + * - Conseiller : bouton -> Stratégie + * - Socket.IO non bloquant + notifications locales + * + * Portefeuille (Step 1): + * - Quantité BTC stockée localement (AsyncStorage) + * - Valeur totale = quantity * prix BTC + * - Ligne info: "BTC @ prix actuel" */ export default function DashboardScreen() { const { height } = useWindowDimensions(); @@ -36,6 +48,7 @@ export default function DashboardScreen() { const [summary, setSummary] = useState(null); const [settings, setSettings] = useState(null); + const [wallet, setWallet] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -59,15 +72,17 @@ export default function DashboardScreen() { setError(null); setLoading(true); - const [dashboardData, userSettings] = await Promise.all([ + const [dashboardData, userSettings, walletData] = await Promise.all([ fetchDashboardSummary(), loadSettings(), + loadWallet(), ]); if (!isActive) return; setSummary(dashboardData); setSettings(userSettings); + setWallet(walletData); setLastRefreshMs(Date.now()); } catch { if (isActive) setError("Impossible de charger le dashboard."); @@ -174,6 +189,11 @@ export default function DashboardScreen() { return liveAlerts[0]; }, [liveAlerts]); + const walletTotalValue = useMemo(() => { + if (!wallet || !summary) return null; + return wallet.quantity * summary.price; + }, [wallet, summary]); + if (loading) { return ( @@ -190,7 +210,7 @@ export default function DashboardScreen() { ); } - if (!summary || !settings) { + if (!summary || !settings || !wallet) { return ( Initialisation… @@ -234,27 +254,32 @@ export default function DashboardScreen() { - {/* 2) PORTEFEUILLE (pas encore cliquable car pas d’écran Wallet dédié) */} - - Portefeuille + {/* 2) PORTEFEUILLE — cliquable => Wallet */} + navigation.navigate("Wallet" as never)}> + + Portefeuille - - Valeur Totale : - 10 000 {settings.currency} - + + Quantité BTC : + {wallet.quantity.toFixed(6)} BTC + - {!compact && ( - - Step 1 : mono-utilisateur / mono-crypto + + Valeur Totale : + + {walletTotalValue !== null ? `${walletTotalValue.toFixed(2)} ${settings.currency}` : "—"} + + + + {/* ✅ Ligne informative : BTC @ prix actuel */} + + BTC @ {summary.price.toFixed(2)} {settings.currency} - )} - + + {/* 3) URGENCE — cliquable => Alertes */} - navigation.navigate("Alerts" as never)} - > + navigation.navigate("Alerts" as never)}> Urgence @@ -264,8 +289,7 @@ export default function DashboardScreen() { {urgentAlert.alertLevel} : {urgentAlert.reason} - {urgentAlert.action} {urgentAlert.pair} —{" "} - {(urgentAlert.confidence * 100).toFixed(0)}% + {urgentAlert.action} {urgentAlert.pair} — {(urgentAlert.confidence * 100).toFixed(0)}% ) : ( @@ -284,20 +308,13 @@ export default function DashboardScreen() { {/* 4) PRIX BTC — cliquable => Historique */} - navigation.navigate("History" as never)} - > + navigation.navigate("History" as never)}> Prix BTC @@ -306,8 +323,7 @@ export default function DashboardScreen() { - Dernière maj :{" "} - {lastRefreshMs ? new Date(lastRefreshMs).toLocaleTimeString() : "—"} + Dernière maj : {lastRefreshMs ? new Date(lastRefreshMs).toLocaleTimeString() : "—"} diff --git a/Wallette/mobile/src/screens/WalletScreen.tsx b/Wallette/mobile/src/screens/WalletScreen.tsx new file mode 100644 index 0000000..0768379 --- /dev/null +++ b/Wallette/mobile/src/screens/WalletScreen.tsx @@ -0,0 +1,240 @@ +import { View, Text, StyleSheet, TouchableOpacity, TextInput, Alert as RNAlert } from "react-native"; +import { useMemo, useState } from "react"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useFocusEffect } from "@react-navigation/native"; +import { useCallback } from "react"; + +import { ui } from "../components/ui/uiStyles"; +import { loadWallet, saveWallet, clearWallet } from "../utils/walletStorage"; +import type { WalletState } from "../models/Wallet"; +import { fetchDashboardSummary } from "../services/dashboardService"; +import { loadSettings } from "../utils/settingsStorage"; +import type { UserSettings } from "../models/UserSettings"; + +/** + * WalletScreen (WF-03 Step 1) + * --------------------------- + * Mono-utilisateur / mono-crypto : BTC uniquement. + * - L'utilisateur encode la quantité de BTC qu'il possède + * - On calcule la valeur estimée via le prix BTC du dashboard + * - Stockage local (AsyncStorage) pour ne pas dépendre de l'API + */ +export default function WalletScreen() { + const [wallet, setWallet] = useState(null); + const [settings, setSettings] = useState(null); + + // Prix BTC actuel (mock aujourd'hui, API demain) + const [btcPrice, setBtcPrice] = useState(null); + + // input texte (évite les bugs de virgule/points) + const [qtyInput, setQtyInput] = useState("0"); + + const [info, setInfo] = useState(null); + + // Recharge quand l’écran reprend le focus (retour depuis autre page) + useFocusEffect( + useCallback(() => { + let active = true; + + async function init() { + setInfo(null); + + const [w, s, dash] = await Promise.all([ + loadWallet(), + loadSettings(), + fetchDashboardSummary(), + ]); + + if (!active) return; + + setWallet(w); + setSettings(s); + setBtcPrice(dash.price); + + setQtyInput(String(w.quantity)); + } + + init(); + + return () => { + active = false; + }; + }, []) + ); + + const parsedQty = useMemo(() => { + const normalized = qtyInput.replace(",", ".").trim(); + const val = Number(normalized); + if (!Number.isFinite(val)) return null; + if (val < 0) return null; + return val; + }, [qtyInput]); + + const totalValue = useMemo(() => { + if (parsedQty === null || btcPrice === null) return null; + return parsedQty * btcPrice; + }, [parsedQty, btcPrice]); + + const lastUpdatedLabel = useMemo(() => { + if (!wallet) return "—"; + return new Date(wallet.updatedAtMs).toLocaleString(); + }, [wallet]); + + const handleSave = async () => { + if (!wallet) return; + + if (parsedQty === null) { + setInfo("Quantité invalide. Exemple : 0.25"); + return; + } + + const updated: WalletState = { + ...wallet, + quantity: parsedQty, + updatedAtMs: Date.now(), + }; + + await saveWallet(updated); + setWallet(updated); + setInfo("Portefeuille sauvegardé ✅"); + }; + + const handleClear = () => { + RNAlert.alert( + "Réinitialiser le portefeuille ?", + "Cela remet la quantité BTC à 0 (stockage local).", + [ + { text: "Annuler", style: "cancel" }, + { + text: "Réinitialiser", + style: "destructive", + onPress: async () => { + await clearWallet(); + const fresh = await loadWallet(); + setWallet(fresh); + setQtyInput("0"); + setInfo("Portefeuille réinitialisé ✅"); + }, + }, + ] + ); + }; + + if (!wallet || !settings) { + return ( + + Chargement du portefeuille… + + ); + } + + return ( + + + Portefeuille + + {/* Carte BTC */} + + BTC + + Quantité détenue + + + + + Prix BTC actuel :{" "} + + {btcPrice !== null ? `${btcPrice.toFixed(2)} ${settings.currency}` : "—"} + + + + + Valeur estimée :{" "} + + {totalValue !== null ? `${totalValue.toFixed(2)} ${settings.currency}` : "—"} + + + + {/* ✅ Dernière mise à jour */} + + Dernière mise à jour :{" "} + {lastUpdatedLabel} + + + + Enregistrer + + + + Réinitialiser + + + {!!info && {info}} + + + {/* Carte info Step */} + + Step 1 + + Mono-utilisateur / mono-crypto (BTC). Step 3 : portefeuille multi-cryptos + valeur globale. + + + + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: ui.screen.backgroundColor, + }, + screenTitle: { + fontSize: 22, + fontWeight: "900", + marginBottom: 12, + color: "#0f172a", + }, + boldInline: { + fontWeight: "900", + color: "#0f172a", + }, + + input: { + borderWidth: 1, + borderColor: "#e5e7eb", + borderRadius: 10, + paddingHorizontal: 12, + paddingVertical: 10, + marginTop: 8, + backgroundColor: "#fff", + color: "#0f172a", + }, + + fullButton: { + flexGrow: 0, + flexBasis: "auto", + width: "100%", + marginTop: 12, + }, + + secondaryButton: { + marginTop: 10, + paddingVertical: 10, + borderRadius: 10, + borderWidth: 1, + borderColor: "#e5e7eb", + alignItems: "center", + backgroundColor: "#fff", + }, + secondaryButtonText: { + fontWeight: "900", + color: "#dc2626", + }, +}); \ No newline at end of file diff --git a/Wallette/mobile/src/utils/walletStorage.ts b/Wallette/mobile/src/utils/walletStorage.ts new file mode 100644 index 0000000..f74b2aa --- /dev/null +++ b/Wallette/mobile/src/utils/walletStorage.ts @@ -0,0 +1,39 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import type { WalletState } from "../models/Wallet"; + +const KEY = "walletStep1"; + +/** + * Valeurs par défaut : wallet vide. + */ +const DEFAULT_WALLET: WalletState = { + assetSymbol: "BTC", + quantity: 0, + updatedAtMs: Date.now(), +}; + +export async function loadWallet(): Promise { + const raw = await AsyncStorage.getItem(KEY); + if (!raw) return DEFAULT_WALLET; + + try { + const parsed = JSON.parse(raw) as Partial; + return { ...DEFAULT_WALLET, ...parsed }; + } catch { + return DEFAULT_WALLET; + } +} + +export async function saveWallet(wallet: WalletState): Promise { + const safe: WalletState = { + assetSymbol: "BTC", + quantity: Number.isFinite(wallet.quantity) ? wallet.quantity : 0, + updatedAtMs: Date.now(), + }; + + await AsyncStorage.setItem(KEY, JSON.stringify(safe)); +} + +export async function clearWallet(): Promise { + await AsyncStorage.removeItem(KEY); +} \ No newline at end of file