From: Thibaud Moustier Date: Wed, 25 Feb 2026 14:10:45 +0000 (+0100) Subject: Mobile : StrategyScreen created + modification X-Git-Url: https://git.digitality.be/?a=commitdiff_plain;h=5c9e3e6e4298dc712186608df310980e86e827b6;p=pdw25-26 Mobile : StrategyScreen created + modification --- diff --git a/Wallette/mobile/App.tsx b/Wallette/mobile/App.tsx index 3498460..4540509 100644 --- a/Wallette/mobile/App.tsx +++ b/Wallette/mobile/App.tsx @@ -5,6 +5,7 @@ import DashboardScreen from "./src/screens/DashboardScreen"; import SettingsScreen from "./src/screens/SettingsScreen"; import HistoryScreen from "./src/screens/HistoryScreen"; import AlertsScreen from "./src/screens/AlertsScreen"; +import StrategyScreen from "./src/screens/StrategyScreen"; // Types des routes (pour éviter les erreurs de navigation) @@ -13,6 +14,7 @@ export type RootStackParamList = { Settings: undefined; History: undefined; Alerts: undefined; + Strategy: undefined; }; const Stack = createNativeStackNavigator(); @@ -34,15 +36,22 @@ export default function App() { /> + name="Alerts" + component={AlertsScreen} + options={{ title: "Alertes" }} + /> + + diff --git a/Wallette/mobile/src/components/StrategyCard.tsx b/Wallette/mobile/src/components/StrategyCard.tsx index 1a940e7..d783848 100644 --- a/Wallette/mobile/src/components/StrategyCard.tsx +++ b/Wallette/mobile/src/components/StrategyCard.tsx @@ -1,82 +1,73 @@ -import { View, Text } from "react-native"; -import type { - DashboardSummary, - TradeDecision, - AlertLevel, -} from "../types/DashboardSummary"; +import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; +import type { DashboardSummary } from "../types/DashboardSummary"; +import type { UserSettings } from "../models/UserSettings"; import { ui } from "./ui/uiStyles"; -type Props = { - summary: DashboardSummary; -}; - -function getDecisionColor(decision: TradeDecision): string { - switch (decision) { - case "BUY": - return "#16a34a"; - case "SELL": - return "#dc2626"; - case "STOP_LOSS": - return "#991b1b"; - case "HOLD": - default: - return "#ca8a04"; - } -} - -function getAlertColor(level: AlertLevel): string { - switch (level) { - case "CRITICAL": - return "#b91c1c"; - case "WARNING": - return "#ca8a04"; - case "INFO": - default: - return "#2563eb"; - } -} - /** * StrategyCard * ------------ - * Affiche la stratégie + décision (BUY/SELL/HOLD/STOP_LOSS) - * + niveau d'alerte (CRITICAL/WARNING/INFO) - * + confiance et raison. + * Affiche la décision (BUY/SELL/HOLD/STOP_LOSS), + * la justification, et la stratégie choisie par l'utilisateur. * - * Les enums sont affichés en badges ("pill") pour être lisibles sur mobile. + * On propose un bouton "Changer stratégie" qui envoie vers l'écran Strategy. */ -export default function StrategyCard({ summary }: Props) { - const decisionColor = getDecisionColor(summary.decision); - const alertColor = getAlertColor(summary.alertLevel); +type Props = { + summary: DashboardSummary; + settings: UserSettings; + onGoStrategy: () => void; +}; +export default function StrategyCard({ summary, settings, onGoStrategy }: Props) { return ( - Stratégie + Conseiller - {summary.strategy} + {/* Décision principale */} + {summary.decision} - {/* Badge décision */} - - - {summary.decision} - - - - {/* Badge niveau d'alerte */} - - - {summary.alertLevel} - - + {/* Justification (centrée) */} + + Pourquoi ? {summary.reason} + - - Confiance - - {(summary.confidence * 100).toFixed(1)} % + {/* Stratégie sélectionnée */} + + + Stratégie sélectionnée :{" "} + {settings.selectedStrategyKey} - {summary.reason} + {/* Bouton vers l'écran stratégie */} + + Changer stratégie + ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + centerText: { + textAlign: "center", + }, + + boldInline: { + fontWeight: "900", + color: "#0f172a", + }, + + /** + * Important : + * ui.button est prévu pour une grille (ActionsCard) -> flexGrow + flexBasis. + * Ici on veut un bouton "plein largeur", donc on neutralise. + */ + fullButton: { + flexGrow: 0, + flexBasis: "auto", + width: "100%", + marginTop: 12, + }, +}); \ No newline at end of file diff --git a/Wallette/mobile/src/mocks/strategies.mock.ts b/Wallette/mobile/src/mocks/strategies.mock.ts new file mode 100644 index 0000000..f2a2efd --- /dev/null +++ b/Wallette/mobile/src/mocks/strategies.mock.ts @@ -0,0 +1,36 @@ +import type { StrategyOption } from "../types/Strategy"; + +/** + * Mock Strategies + * --------------- + * Step 2 : mono-user, mono-crypto, multi-stratégies. + * On garde une liste fixe, et plus tard on branchera l'API. + */ +export async function getStrategies(): Promise { + return [ + { + key: "RSI_SIMPLE", + label: "RSI simple", + description: "BUY si RSI bas, SELL si RSI haut (version simplifiée).", + risk: "SAFE", + }, + { + key: "MA_CROSS", + label: "Moyennes mobiles (cross)", + description: "BUY quand la moyenne courte croise au-dessus de la longue.", + risk: "NORMAL", + }, + { + key: "MACD_BASIC", + label: "MACD basique", + description: "Analyse MACD simplifiée (signal/ligne).", + risk: "NORMAL", + }, + { + key: "HOLD_ONLY", + label: "Hold only", + description: "Toujours HOLD (utile pour comparer / debug).", + risk: "SAFE", + }, + ]; +} \ No newline at end of file diff --git a/Wallette/mobile/src/models/UserSettings.ts b/Wallette/mobile/src/models/UserSettings.ts index c4f583b..97b7181 100644 --- a/Wallette/mobile/src/models/UserSettings.ts +++ b/Wallette/mobile/src/models/UserSettings.ts @@ -18,6 +18,10 @@ export interface UserSettings { // Rafraîchissement dashboard refreshMode: "manual" | "auto"; - // ✅ Notifications locales (Expo) + // Notifications locales (Expo) notificationsEnabled: boolean; + + // Stratégie choisie (persistée) + // Exemple: "RSI_SIMPLE" + selectedStrategyKey: string; } \ No newline at end of file diff --git a/Wallette/mobile/src/screens/DashboardScreen.tsx b/Wallette/mobile/src/screens/DashboardScreen.tsx index be6e15c..7064954 100644 --- a/Wallette/mobile/src/screens/DashboardScreen.tsx +++ b/Wallette/mobile/src/screens/DashboardScreen.tsx @@ -233,7 +233,13 @@ export default function DashboardScreen() { )} - + + navigation.navigate("Strategy" as never)} + /> + (null); - - // Petit message utilisateur (permission refusée, etc.) const [infoMessage, setInfoMessage] = useState(null); useEffect(() => { @@ -43,21 +38,14 @@ export default function SettingsScreen() { setSettings({ ...settings, refreshMode: newMode }); }; - /** - * Notifications : - * - si l'utilisateur active -> on demande la permission - * - si refus -> on reste désactivé + message - */ const toggleNotifications = async () => { setInfoMessage(null); - // si on désactive, pas besoin de permission if (settings.notificationsEnabled) { setSettings({ ...settings, notificationsEnabled: false }); return; } - // si on active -> demander permission const granted = await requestNotificationPermission(); if (!granted) { @@ -85,11 +73,9 @@ export default function SettingsScreen() { Devise Actuelle : {settings.currency} - - - Changer devise - - + + Changer devise + {/* Carte : Refresh */} @@ -97,11 +83,9 @@ export default function SettingsScreen() { Rafraîchissement Mode : {settings.refreshMode} - - - Changer mode - - + + Changer mode + {/* Carte : Notifications */} @@ -111,20 +95,17 @@ export default function SettingsScreen() { Statut : {settings.notificationsEnabled ? "ON" : "OFF"} - - - - {settings.notificationsEnabled ? "Désactiver" : "Activer"} les - notifications - - - + + + {settings.notificationsEnabled ? "Désactiver" : "Activer"} les notifications + + {!!infoMessage && {infoMessage}} {/* Bouton Save */} - + Sauvegarder @@ -147,9 +128,21 @@ const styles = StyleSheet.create({ marginTop: 10, opacity: 0.8, }, - // On rend le bouton Save plus large (full width) + + /** + * fullButton + * ---------- + * On neutralise les propriétés "grille" de ui.button (flexGrow/flexBasis), + * car elles sont utiles dans ActionsCard, mais pas dans Settings. + */ + fullButton: { + flexGrow: 0, + flexBasis: "auto", + width: "100%", + marginTop: 10, + }, + saveButton: { - flexBasis: "100%", - marginTop: 4, + marginBottom: 10, }, }); \ No newline at end of file diff --git a/Wallette/mobile/src/screens/StrategyScreen.tsx b/Wallette/mobile/src/screens/StrategyScreen.tsx new file mode 100644 index 0000000..f3ebfdc --- /dev/null +++ b/Wallette/mobile/src/screens/StrategyScreen.tsx @@ -0,0 +1,144 @@ +import { View, Text, StyleSheet, TouchableOpacity, FlatList } from "react-native"; +import { useEffect, useState } from "react"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { ui } from "../components/ui/uiStyles"; +import { loadSettings, saveSettings } from "../utils/settingsStorage"; + +import type { UserSettings } from "../models/UserSettings"; +import type { StrategyKey, StrategyOption } from "../types/Strategy"; +import { fetchStrategies } from "../services/strategyService"; + +/** + * StrategyScreen + * -------------- + * L'utilisateur sélectionne une stratégie. + * La stratégie choisie est persistée dans UserSettings (AsyncStorage). + * + * Plus tard : on pourra aussi appeler POST /api/strategy/select, + * sans changer l'écran. + */ +export default function StrategyScreen() { + const [settings, setSettings] = useState(null); + const [strategies, setStrategies] = useState([]); + const [loading, setLoading] = useState(true); + const [info, setInfo] = useState(null); + + useEffect(() => { + let active = true; + + async function init() { + try { + setLoading(true); + const [s, list] = await Promise.all([loadSettings(), fetchStrategies()]); + if (!active) return; + + setSettings(s); + setStrategies(list); + } finally { + if (active) setLoading(false); + } + } + + init(); + + return () => { + active = false; + }; + }, []); + + if (loading || !settings) { + return ( + + Chargement des stratégies… + + ); + } + + const isSelected = (key: StrategyKey) => settings.selectedStrategyKey === key; + + const handleSelect = async (key: StrategyKey) => { + const updated: UserSettings = { ...settings, selectedStrategyKey: key }; + setSettings(updated); + + // Sauvegarde immédiate (simple et défendable) + await saveSettings(updated); + + setInfo(`Stratégie sélectionnée : ${key}`); + }; + + return ( + + it.key} + ListHeaderComponent={ + + Stratégie + + Choisis une stratégie. Elle sera affichée sur le Dashboard. + + + + Actuelle : {settings.selectedStrategyKey} + + + {!!info && {info}} + + } + renderItem={({ item }) => ( + + + {item.label} + {item.risk} + + + {item.description} + + handleSelect(item.key)} + > + + {isSelected(item.key) ? "Sélectionnée ✅" : "Sélectionner"} + + + + )} + /> + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: ui.screen.backgroundColor, + }, + + boldInline: { + fontWeight: "900", + color: "#0f172a", + }, + + fullButton: { + flexGrow: 0, + flexBasis: "auto", + width: "100%", + marginTop: 12, + }, + + cardSelected: { + borderColor: "#16a34a", + }, + + btnSelected: { + opacity: 0.9, + }, + + riskTag: { + fontWeight: "900", + opacity: 0.75, + }, +}); \ No newline at end of file diff --git a/Wallette/mobile/src/services/strategyService.ts b/Wallette/mobile/src/services/strategyService.ts new file mode 100644 index 0000000..2d7a558 --- /dev/null +++ b/Wallette/mobile/src/services/strategyService.ts @@ -0,0 +1,23 @@ +import type { StrategyOption, StrategyKey } from "../types/Strategy"; +import { getStrategies } from "../mocks/strategies.mock"; + +/** + * strategyService + * --------------- + * Aujourd'hui : mock + * Demain : REST + * + * REST attendu (contrat groupe) : + * - POST /api/strategy/select + */ +export async function fetchStrategies(): Promise { + return await getStrategies(); +} + +/** + * Placeholder : plus tard on fera le POST ici. + * Pour le moment, la sélection est gérée via AsyncStorage (settings). + */ +export async function selectStrategy(_strategyKey: StrategyKey): Promise { + return; +} \ No newline at end of file diff --git a/Wallette/mobile/src/types/Strategy.ts b/Wallette/mobile/src/types/Strategy.ts new file mode 100644 index 0000000..a8c3440 --- /dev/null +++ b/Wallette/mobile/src/types/Strategy.ts @@ -0,0 +1,19 @@ +/** + * Strategy + * -------- + * Contrat simple côté mobile. + * Plus tard: ces données viendront de l'API / DB. + */ + +export type StrategyKey = + | "RSI_SIMPLE" + | "MA_CROSS" + | "MACD_BASIC" + | "HOLD_ONLY"; + +export interface StrategyOption { + key: StrategyKey; + label: string; + description: string; + risk: "SAFE" | "NORMAL" | "AGGRESSIVE"; +} \ No newline at end of file diff --git a/Wallette/mobile/src/utils/settingsStorage.ts b/Wallette/mobile/src/utils/settingsStorage.ts index d86718a..07570d7 100644 --- a/Wallette/mobile/src/utils/settingsStorage.ts +++ b/Wallette/mobile/src/utils/settingsStorage.ts @@ -13,9 +13,10 @@ const DEFAULT_SETTINGS: UserSettings = { favoriteSymbol: "BTC", alertPreference: "critical", refreshMode: "manual", - - // ✅ par défaut désactivé (l'utilisateur choisit) notificationsEnabled: false, + + // ✅ stratégie par défaut + selectedStrategyKey: "RSI_SIMPLE", }; export async function loadSettings(): Promise {