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)
Settings: undefined;
History: undefined;
Alerts: undefined;
+ Strategy: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
/>
<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>
-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
--- /dev/null
+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
// 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
)}
<MarketCard summary={summary} settings={settings} />
- <StrategyCard summary={summary} />
+
+ <StrategyCard
+ summary={summary}
+ settings={settings}
+ onGoStrategy={() => navigation.navigate("Strategy" as never)}
+ />
+
<WalletCard settings={settings} />
<ActionsCard
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(() => {
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) {
<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 */}
<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 */}
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>
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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/**
+ * 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
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> {