import React, { useState, useCallback, useEffect } from "react";
import {
View,
StyleSheet,
Image,
ScrollView,
Platform,
AppState,
} from "react-native";
import { Title } from "react-native-paper";
import { Ionicons, Entypo } from "@expo/vector-icons";
import {
permissionsActions,
usePermissionsState,
permissionWizardActions,
} from "~/stores";
import { createStyles, useTheme } from "~/theme";
import openSettings from "~/lib/native/openSettings";
import {
RequestDisableOptimization,
BatteryOptEnabled,
} from "react-native-battery-optimization-check";
import requestPermissionLocationBackground from "~/permissions/requestPermissionLocationBackground";
import requestPermissionMotion from "~/permissions/requestPermissionMotion";
import CustomButton from "~/components/CustomButton";
import Text from "~/components/Text";
const skipMessages = [
"Non merci, je préfère rester égoïste",
"Les héros ? Très peu pour moi !",
"J'ai peur des responsabilités...",
"Je suis trop douillet pour être un héros...",
"Non merci, je préfère rester sur mon canapé",
"Les héros ? Ça me donne des boutons !",
"J'ai une allergie aux bonnes actions",
"Désolé, mon chat a besoin de moi",
];
const HeroMode = () => {
const [requesting, setRequesting] = useState(false);
const [hasAttempted, setHasAttempted] = useState(false);
const [hasRetried, setHasRetried] = useState(false);
const [batteryOptimizationEnabled, setBatteryOptimizationEnabled] =
useState(null);
const [batteryOptAttempted, setBatteryOptAttempted] = useState(false);
const [batteryOptInProgress, setBatteryOptInProgress] = useState(false);
const permissions = usePermissionsState([
"locationBackground",
"motion",
"batteryOptimizationDisabled",
]);
const theme = useTheme();
const [skipMessage] = useState(() => {
const randomIndex = Math.floor(Math.random() * skipMessages.length);
return skipMessages[randomIndex];
});
const handleNext = useCallback(() => {
permissionWizardActions.setCurrentStep("success");
}, []);
const handleSkip = useCallback(() => {
permissionWizardActions.setCurrentStep("skipInfo");
}, []);
const handleBatteryOptimization = useCallback(async () => {
if (Platform.OS !== "android") {
permissionsActions.setBatteryOptimizationDisabled(true);
return true;
}
try {
setBatteryOptInProgress(true);
// Check if battery optimization is enabled
const isEnabled = await BatteryOptEnabled();
setBatteryOptimizationEnabled(isEnabled);
if (isEnabled) {
console.log(
"Battery optimization is enabled, requesting to disable...",
);
// Request to disable battery optimization (opens Android Settings)
RequestDisableOptimization();
setBatteryOptAttempted(true);
// Return false to indicate user needs to complete action in Settings
return false;
} else {
console.log("Battery optimization already disabled");
permissionsActions.setBatteryOptimizationDisabled(true);
return true;
}
} catch (error) {
console.error("Error handling battery optimization:", error);
setBatteryOptAttempted(true);
return false;
} finally {
setBatteryOptInProgress(false);
}
}, []);
const handleRequestPermissions = useCallback(async () => {
setRequesting(true);
try {
// Don't change step immediately to avoid race conditions
console.log("Starting permission requests...");
// Request battery optimization FIRST (opens Android Settings)
// This prevents the bubbling issue by handling Settings-based permissions before in-app dialogs
const batteryOptDisabled = await handleBatteryOptimization();
console.log("Battery optimization disabled:", batteryOptDisabled);
// Request motion permission second
const motionGranted = await requestPermissionMotion.requestPermission();
permissionsActions.setMotion(motionGranted);
console.log("Motion permission:", motionGranted);
// Request background location last (after user returns from Settings if needed)
const locationGranted = await requestPermissionLocationBackground();
permissionsActions.setLocationBackground(locationGranted);
console.log("Location background permission:", locationGranted);
// Only set step to tracking after all permission requests are complete
permissionWizardActions.setCurrentStep("tracking");
// Check if we should proceed to success immediately
if (locationGranted && motionGranted && batteryOptDisabled) {
permissionWizardActions.setHeroPermissionsGranted(true);
// Don't navigate immediately, let the useEffect handle it
}
} catch (error) {
console.error("Error requesting permissions:", error);
}
setRequesting(false);
setHasAttempted(true);
}, [handleBatteryOptimization]);
const handleRetry = useCallback(async () => {
// Re-check battery optimization status before retrying
if (Platform.OS === "android") {
try {
const isEnabled = await BatteryOptEnabled();
setBatteryOptimizationEnabled(isEnabled);
// If battery optimization is now disabled, update the store
if (!isEnabled) {
console.log("Battery optimization now disabled after retry");
permissionsActions.setBatteryOptimizationDisabled(true);
}
} catch (error) {
console.error("Error re-checking battery optimization:", error);
}
}
// Only request permissions again if some are still missing
const needsRetry =
!permissions.locationBackground ||
!permissions.motion ||
(Platform.OS === "android" && batteryOptimizationEnabled);
if (needsRetry) {
await handleRequestPermissions();
}
setHasRetried(true);
}, [handleRequestPermissions, permissions, batteryOptimizationEnabled]);
const allGranted =
permissions.locationBackground &&
permissions.motion &&
(Platform.OS === "ios" || !batteryOptimizationEnabled);
// Check battery optimization status on mount
useEffect(() => {
const checkInitialBatteryOptimization = async () => {
if (Platform.OS === "android") {
try {
const isEnabled = await BatteryOptEnabled();
setBatteryOptimizationEnabled(isEnabled);
// If already disabled, update the store
if (!isEnabled) {
permissionsActions.setBatteryOptimizationDisabled(true);
}
} catch (error) {
console.error("Error checking initial battery optimization:", error);
}
} else {
// iOS doesn't have battery optimization, so mark as disabled
permissionsActions.setBatteryOptimizationDisabled(true);
}
};
checkInitialBatteryOptimization();
}, []);
// Listen for app state changes to re-check battery optimization when user returns from settings
useEffect(() => {
const handleAppStateChange = async (nextAppState) => {
if (
nextAppState === "active" &&
Platform.OS === "android" &&
batteryOptAttempted
) {
console.log("App became active, re-checking battery optimization...");
try {
const isEnabled = await BatteryOptEnabled();
setBatteryOptimizationEnabled(isEnabled);
if (!isEnabled) {
console.log(
"Battery optimization disabled after returning from settings",
);
permissionsActions.setBatteryOptimizationDisabled(true);
}
} catch (error) {
console.error(
"Error re-checking battery optimization on app focus:",
error,
);
}
}
};
const subscription = AppState.addEventListener(
"change",
handleAppStateChange,
);
return () => {
subscription?.remove();
};
}, [batteryOptAttempted]);
useEffect(() => {
if (hasAttempted && allGranted) {
handleNext();
}
}, [hasAttempted, allGranted, handleNext]);
const styles = useStyles();
const renderWarnings = () => {
const warnings = [];
if (!permissions.motion) {
warnings.push(
"Sans la détection de mouvement, la localisation en arrière-plan ne pourra pas fonctionner.",
);
}
if (!permissions.locationBackground) {
warnings.push(
"Sans la localisation en arrière-plan, vous ne pourrez pas être alerté des situations d'urgence à proximité lorsque l'application est fermée.",
);
}
// Battery optimization warning is now handled in the Android settings box
return warnings.length > 0 ? (
{warnings.map((warning, index) => (
{warning}
))}
) : null;
};
const renderAndroidPermissionWarning = () => {
const hasBatteryOptimizationIssue =
batteryOptimizationEnabled && batteryOptAttempted;
return (
Paramètres Android
{hasBatteryOptimizationIssue && (
L'optimisation de la
batterie est encore activée. L'application pourrait ne pas
fonctionner correctement en arrière-plan.
)}
Sur Android, les permissions peuvent être automatiquement révoquées si
l'application n'est pas utilisée pendant une longue période.
Pour garantir le bon fonctionnement de l'application :
1. Accédez aux paramètres de l'application
2. Recherchez la section "Autorisations" ou "Permissions"
3. Désactivez l'option "Supprimer les autorisations si l'application
n'est pas utilisée" (l'emplacement exact peut varier selon votre
version d'Android)
{hasBatteryOptimizationIssue && (
Pour désactiver l'optimisation de la batterie :
4. Recherchez "Batterie" ou "Optimisation de la batterie"
5. Trouvez cette application dans la liste
6. Sélectionnez "Ne pas optimiser" ou "Désactiver l'optimisation"
)}
Ouvrir les paramètres
);
};
const renderPlatformWarning = () => {
if (Platform.OS === "ios") {
return renderIOSPermissionWarning();
} else if (Platform.OS === "android") {
return renderAndroidPermissionWarning();
}
return null;
};
const renderIOSPermissionWarning = () => {
return (
Paramètres iOS
Pour garantir le bon fonctionnement de l'application en arrière-plan,
quelques réglages supplémentaires sont nécessaires.
1. Activez l'actualisation en arrière-plan :
• {"Réglages > Général > Actualisation en arrière-plan"}
• Activez l'option pour cette application
2. Attention aux modes qui peuvent limiter le fonctionnement :
• Le mode économie d'énergie
• Le mode concentration (Ne pas déranger)
Ouvrir les réglages
);
};
const renderButton = () => {
if (!hasAttempted) {
return (
{batteryOptInProgress
? "Traitement de l'optimisation de la batterie..."
: "J'accorde les permissions"}
);
}
if (allGranted) {
return (
Suivant
);
}
return (
<>
{skipMessage}
{batteryOptInProgress
? "Vérification en cours..."
: "Réessayer d'accorder les permissions"}
{hasRetried && (
<>
Si les permissions ne sont pas accordées, vous devez les activer
manuellement dans les paramètres de votre téléphone.
Paramètres
>
)}
>
);
};
return (
Rejoignez les vrais{"\n"}
Soyez prêt à agir
Pas besoin de super-pouvoirs pour être un héros, il vous suffit
simplement d'activer les autorisations nécessaires qui permettront
de vous alerter. Ensuite, répondre présent pour apporter votre aide
fera la différence !
Permissions requises
Localisation en arrière-plan : pour être alerté des situations
d'urgence à proximité même lorsque l'application est fermée.
Détection de mouvement : pour optimiser la consommation de
batterie lors de la localisation en arrière-plan, aucune
donnée de mouvement n'est stockée ni transmise.
{Platform.OS === "android" && (
Optimisation de la batterie : désactiver l'optimisation de
la batterie pour cette application afin qu'elle puisse
fonctionner correctement en arrière-plan.
)}
Important
N'oubliez pas de garder la localisation de votre téléphone
activée pour que l'application puisse fonctionner correctement !
{!allGranted && hasAttempted && renderWarnings()}
{renderPlatformWarning()}
{renderButton()}
);
};
const useStyles = createStyles(({ wp, hp, scaleText, theme: { colors } }) => ({
container: {
flex: 1,
},
scrollView: {
flex: 1,
},
content: {
padding: 20,
},
heroHeader: {
alignItems: "center",
marginBottom: 30,
},
heroImage: {
width: 120,
height: 120,
marginBottom: 10,
},
title: {
fontSize: 28,
fontWeight: "bold",
textAlign: "center",
marginBottom: 20,
},
subtitle: {
fontSize: 22,
},
description: {
fontSize: 16,
marginBottom: 30,
lineHeight: 24,
textAlign: "left",
color: colors.onSurfaceVariant,
},
section: {
marginBottom: 25,
},
sectionTitle: {
fontSize: 18,
fontWeight: "bold",
color: colors.primary,
marginBottom: 15,
},
permissionList: {
gap: 15,
},
permissionItem: {
flexDirection: "row",
alignItems: "flex-start",
},
permissionContent: {
flex: 1,
},
icon: {
marginRight: 10,
marginTop: 2,
color: colors.primary,
},
permissionText: {
fontSize: 16,
flex: 1,
lineHeight: 22,
textAlign: "left",
color: colors.onSurfaceVariant,
},
warningsContainer: {
backgroundColor: colors.surfaceVariant,
padding: 15,
borderRadius: 8,
marginBottom: 20,
borderWidth: 1,
borderColor: colors.error,
},
warning: {
fontSize: 15,
lineHeight: 20,
textAlign: "left",
color: colors.onSurfaceVariant,
},
settingsHint: {
fontSize: 14,
lineHeight: 20,
textAlign: "center",
marginBottom: 10,
fontStyle: "italic",
color: colors.onSurfaceVariant,
},
buttonContainer: {
marginTop: 20,
gap: 10,
},
// Android styles
androidWarning: {
backgroundColor: colors.surfaceVariant,
padding: 20,
borderRadius: 8,
marginBottom: 20,
borderWidth: 1,
borderColor: colors.warn,
},
androidWarningCritical: {
borderColor: colors.error,
borderWidth: 2,
},
androidWarningHeader: {
flexDirection: "row",
alignItems: "center",
marginBottom: 15,
gap: 10,
},
androidWarningTitle: {
fontSize: 18,
fontWeight: "bold",
color: colors.warn,
},
androidWarningDescription: {
fontSize: 16,
lineHeight: 22,
color: colors.onSurfaceVariant,
marginBottom: 15,
},
androidWarningSteps: {
marginBottom: 15,
},
androidWarningText: {
fontSize: 16,
lineHeight: 22,
color: colors.onSurfaceVariant,
marginBottom: 10,
},
androidWarningStep: {
fontSize: 16,
lineHeight: 22,
color: colors.onSurfaceVariant,
marginLeft: 15,
marginBottom: 5,
},
androidSettingsButton: {
marginTop: 5,
color: colors.primary,
},
batteryOptimizationAlert: {
backgroundColor: colors.errorContainer || colors.surfaceVariant,
padding: 15,
borderRadius: 6,
marginBottom: 15,
borderWidth: 1,
borderColor: colors.error,
},
batteryOptimizationAlertText: {
fontSize: 15,
lineHeight: 20,
fontWeight: "500",
},
batteryOptimizationText: {
fontWeight: "600",
color: colors.error,
},
// iOS styles
iosWarning: {
backgroundColor: colors.surfaceVariant,
padding: 20,
borderRadius: 8,
marginBottom: 20,
borderWidth: 1,
borderColor: colors.warn,
},
iosWarningHeader: {
flexDirection: "row",
alignItems: "center",
marginBottom: 15,
gap: 10,
},
iosWarningTitle: {
fontSize: 18,
fontWeight: "bold",
color: colors.warn,
},
iosWarningDescription: {
fontSize: 16,
lineHeight: 22,
color: colors.onSurfaceVariant,
marginBottom: 15,
},
iosWarningSteps: {
marginBottom: 15,
},
iosWarningText: {
fontSize: 16,
lineHeight: 22,
color: colors.onSurfaceVariant,
marginBottom: 10,
},
iosWarningStep: {
fontSize: 16,
lineHeight: 22,
color: colors.onSurfaceVariant,
marginLeft: 15,
marginBottom: 5,
},
iosSettingsButton: {
marginTop: 5,
color: colors.primary,
},
}));
export default HeroMode;