]> git.digitality.be Git - pdw25-26/commitdiff
Mobile : StrategyScreen created + modification
authorThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 14:10:45 +0000 (15:10 +0100)
committerThibaud Moustier <thibaudmoustier0@gmail.com>
Wed, 25 Feb 2026 14:10:45 +0000 (15:10 +0100)
Wallette/mobile/App.tsx
Wallette/mobile/src/components/StrategyCard.tsx
Wallette/mobile/src/mocks/strategies.mock.ts [new file with mode: 0644]
Wallette/mobile/src/models/UserSettings.ts
Wallette/mobile/src/screens/DashboardScreen.tsx
Wallette/mobile/src/screens/SettingsScreen.tsx
Wallette/mobile/src/screens/StrategyScreen.tsx [new file with mode: 0644]
Wallette/mobile/src/services/strategyService.ts [new file with mode: 0644]
Wallette/mobile/src/types/Strategy.ts [new file with mode: 0644]
Wallette/mobile/src/utils/settingsStorage.ts

index 34984601c94f792372c86433e9bb803a227751a7..454050975f5b93cc43864533a11e911a5c82c8e8 100644 (file)
@@ -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<RootStackParamList>();
@@ -34,15 +36,22 @@ export default function App() {
         />
 
         <Stack.Screen 
-        name="History" 
-        component={HistoryScreen} 
-        options={{ title: "Historique" }} 
+          name="History" 
+          component={HistoryScreen} 
+          options={{ title: "Historique" }} 
         />
 
         <Stack.Screen 
-        name="Alerts" 
-        component={AlertsScreen} 
-        options={{ title: "Alertes" }} />
+          name="Alerts" 
+          component={AlertsScreen} 
+          options={{ title: "Alertes" }} 
+          />
+
+        <Stack.Screen 
+          name="Strategy" 
+          component={StrategyScreen} 
+          options={{ title: "Stratégie" }} 
+          />
         
       </Stack.Navigator>
     </NavigationContainer>
index 1a940e7f5abb30c066056f5fcf4bbbdeaf292b94..d783848e814244da766739f820280f88d68450b8 100644 (file)
@@ -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 (
     <View style={ui.card}>
-      <Text style={ui.title}>Stratégie</Text>
+      <Text style={ui.title}>Conseiller</Text>
 
-      <Text style={ui.valueBold}>{summary.strategy}</Text>
+      {/* Décision principale */}
+      <Text style={ui.bigCenter}>{summary.decision}</Text>
 
-      {/* Badge décision */}
-      <View style={[ui.badge, { backgroundColor: `${decisionColor}22` }]}>
-        <Text style={[ui.badgeText, { color: decisionColor }]}>
-          {summary.decision}
-        </Text>
-      </View>
-
-      {/* Badge niveau d'alerte */}
-      <View style={[ui.badge, { backgroundColor: `${alertColor}22` }]}>
-        <Text style={[ui.badgeText, { color: alertColor }]}>
-          {summary.alertLevel}
-        </Text>
-      </View>
+      {/* Justification (centrée) */}
+      <Text style={[ui.muted, styles.centerText]}>
+        Pourquoi ? {summary.reason}
+      </Text>
 
-      <View style={[ui.rowBetween, { marginTop: 10 }]}>
-        <Text style={ui.value}>Confiance</Text>
-        <Text style={ui.valueBold}>
-          {(summary.confidence * 100).toFixed(1)} %
+      {/* Stratégie sélectionnée */}
+      <View style={{ marginTop: 10 }}>
+        <Text style={ui.muted}>
+          Stratégie sélectionnée :{" "}
+          <Text style={styles.boldInline}>{settings.selectedStrategyKey}</Text>
         </Text>
       </View>
 
-      <Text style={[ui.muted, { marginTop: 8 }]}>{summary.reason}</Text>
+      {/* Bouton vers l'écran stratégie */}
+      <TouchableOpacity
+        style={[ui.button, styles.fullButton]}
+        onPress={onGoStrategy}
+      >
+        <Text style={ui.buttonText}>Changer stratégie</Text>
+      </TouchableOpacity>
     </View>
   );
-}
\ 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 (file)
index 0000000..f2a2efd
--- /dev/null
@@ -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<StrategyOption[]> {
+  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
index c4f583b86cfa4e2b53fa36fc7ee1a3b9218a2149..97b718120f4bfd82fbac1f8438f714459536ff5d 100644 (file)
@@ -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
index be6e15cfa82f26f66c52f771822eb813530433a5..70649541c12821dcaa395b74c4df6f05f0b648e3 100644 (file)
@@ -233,7 +233,13 @@ export default function DashboardScreen() {
         )}
 
         <MarketCard summary={summary} settings={settings} />
-        <StrategyCard summary={summary} />
+        
+        <StrategyCard
+          summary={summary}
+          settings={settings}
+          onGoStrategy={() => navigation.navigate("Strategy" as never)}
+        />
+
         <WalletCard settings={settings} />
 
         <ActionsCard
index b76423ef17ddc4eabecbf6c0429d641457f31d7a..24c9e776caa7cf3c5794facd8a55ce05395fcf5d 100644 (file)
@@ -5,16 +5,11 @@ import { SafeAreaView } from "react-native-safe-area-context";
 import { loadSettings, saveSettings } from "../utils/settingsStorage";
 import type { UserSettings } from "../models/UserSettings";
 
-// ✅ Notifications Expo
 import { requestNotificationPermission } from "../services/notificationService";
-
-// ✅ UI global
 import { ui } from "../components/ui/uiStyles";
 
 export default function SettingsScreen() {
   const [settings, setSettings] = useState<UserSettings | null>(null);
-
-  // Petit message utilisateur (permission refusée, etc.)
   const [infoMessage, setInfoMessage] = useState<string | null>(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() {
           <Text style={ui.title}>Devise</Text>
           <Text style={ui.value}>Actuelle : {settings.currency}</Text>
 
-          <View style={{ marginTop: 10 }}>
-            <TouchableOpacity style={ui.button} onPress={toggleCurrency}>
-              <Text style={ui.buttonText}>Changer devise</Text>
-            </TouchableOpacity>
-          </View>
+          <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={toggleCurrency}>
+            <Text style={ui.buttonText}>Changer devise</Text>
+          </TouchableOpacity>
         </View>
 
         {/* Carte : Refresh */}
@@ -97,11 +83,9 @@ export default function SettingsScreen() {
           <Text style={ui.title}>Rafraîchissement</Text>
           <Text style={ui.value}>Mode : {settings.refreshMode}</Text>
 
-          <View style={{ marginTop: 10 }}>
-            <TouchableOpacity style={ui.button} onPress={toggleRefreshMode}>
-              <Text style={ui.buttonText}>Changer mode</Text>
-            </TouchableOpacity>
-          </View>
+          <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={toggleRefreshMode}>
+            <Text style={ui.buttonText}>Changer mode</Text>
+          </TouchableOpacity>
         </View>
 
         {/* Carte : Notifications */}
@@ -111,20 +95,17 @@ export default function SettingsScreen() {
             Statut : {settings.notificationsEnabled ? "ON" : "OFF"}
           </Text>
 
-          <View style={{ marginTop: 10 }}>
-            <TouchableOpacity style={ui.button} onPress={toggleNotifications}>
-              <Text style={ui.buttonText}>
-                {settings.notificationsEnabled ? "Désactiver" : "Activer"} les
-                notifications
-              </Text>
-            </TouchableOpacity>
-          </View>
+          <TouchableOpacity style={[ui.button, styles.fullButton]} onPress={toggleNotifications}>
+            <Text style={ui.buttonText}>
+              {settings.notificationsEnabled ? "Désactiver" : "Activer"} les notifications
+            </Text>
+          </TouchableOpacity>
 
           {!!infoMessage && <Text style={styles.infoText}>{infoMessage}</Text>}
         </View>
 
         {/* Bouton Save */}
-        <TouchableOpacity style={[ui.button, styles.saveButton]} onPress={handleSave}>
+        <TouchableOpacity style={[ui.button, styles.fullButton, styles.saveButton]} onPress={handleSave}>
           <Text style={ui.buttonText}>Sauvegarder</Text>
         </TouchableOpacity>
       </View>
@@ -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 (file)
index 0000000..f3ebfdc
--- /dev/null
@@ -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<UserSettings | null>(null);
+  const [strategies, setStrategies] = useState<StrategyOption[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [info, setInfo] = useState<string | null>(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 (
+      <View style={ui.centered}>
+        <Text>Chargement des stratégies…</Text>
+      </View>
+    );
+  }
+
+  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 (
+    <SafeAreaView style={styles.safeArea}>
+      <FlatList
+        contentContainerStyle={ui.container}
+        data={strategies}
+        keyExtractor={(it) => it.key}
+        ListHeaderComponent={
+          <View style={ui.card}>
+            <Text style={ui.title}>Stratégie</Text>
+            <Text style={ui.muted}>
+              Choisis une stratégie. Elle sera affichée sur le Dashboard.
+            </Text>
+
+            <Text style={[ui.muted, { marginTop: 8 }]}>
+              Actuelle : <Text style={styles.boldInline}>{settings.selectedStrategyKey}</Text>
+            </Text>
+
+            {!!info && <Text style={[ui.muted, { marginTop: 8 }]}>{info}</Text>}
+          </View>
+        }
+        renderItem={({ item }) => (
+          <View style={[ui.card, isSelected(item.key) && styles.cardSelected]}>
+            <View style={ui.rowBetween}>
+              <Text style={ui.valueBold}>{item.label}</Text>
+              <Text style={styles.riskTag}>{item.risk}</Text>
+            </View>
+
+            <Text style={[ui.muted, { marginTop: 8 }]}>{item.description}</Text>
+
+            <TouchableOpacity
+              style={[ui.button, styles.fullButton, isSelected(item.key) && styles.btnSelected]}
+              onPress={() => handleSelect(item.key)}
+            >
+              <Text style={ui.buttonText}>
+                {isSelected(item.key) ? "Sélectionnée ✅" : "Sélectionner"}
+              </Text>
+            </TouchableOpacity>
+          </View>
+        )}
+      />
+    </SafeAreaView>
+  );
+}
+
+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 (file)
index 0000000..2d7a558
--- /dev/null
@@ -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<StrategyOption[]> {
+  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<void> {
+  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 (file)
index 0000000..a8c3440
--- /dev/null
@@ -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
index d86718ad087673f9abdd6648c9afb4206bdb606f..07570d727a7eee2c342e08efb94dd3760f92a518 100644 (file)
@@ -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<UserSettings> {