feat: people around radar
This commit is contained in:
parent
236121a73c
commit
becb61967c
8 changed files with 343 additions and 6 deletions
27
src/hooks/useRadarData.js
Normal file
27
src/hooks/useRadarData.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { useLazyQuery } from "@apollo/client";
|
||||
import { useCallback } from "react";
|
||||
import { RADAR_PEOPLE_COUNT_QUERY } from "~/scenes/SendAlert/gql";
|
||||
|
||||
export default function useRadarData() {
|
||||
const [fetchRadarData, { data, loading: isLoading, error }] = useLazyQuery(
|
||||
RADAR_PEOPLE_COUNT_QUERY,
|
||||
{
|
||||
fetchPolicy: "network-only", // Always fetch fresh data
|
||||
errorPolicy: "all",
|
||||
},
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
// Reset is handled by not calling the query again
|
||||
// Apollo will manage the state internally
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data: data?.getOneRadarPeopleCount,
|
||||
isLoading,
|
||||
error,
|
||||
fetchRadarData,
|
||||
reset,
|
||||
hasLocation: true, // Location is now handled server-side via authentication
|
||||
};
|
||||
}
|
|
@ -6,7 +6,7 @@ import { createStyles } from "~/theme";
|
|||
import Text from "~/components/Text";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
|
||||
export default function NotificationsButton() {
|
||||
export default function NotificationsButton({ flex = 1 }) {
|
||||
const navigation = useNavigation();
|
||||
const { hasRegisteredRelatives } = useParamsState(["hasRegisteredRelatives"]);
|
||||
const { newCount } = useNotificationsState(["newCount"]);
|
||||
|
@ -50,9 +50,11 @@ export default function NotificationsButton() {
|
|||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={{ flex }}>
|
||||
<TouchableOpacity
|
||||
style={styles.button}
|
||||
accessibilityLabel={hasNewNotifications ? `Notifications - ${newCount} nouvelles notifications` : "Notifications"}
|
||||
accessibilityRole="button"
|
||||
onPress={() => navigation.navigate("Notifications")}
|
||||
>
|
||||
<MaterialIcons
|
||||
|
@ -86,12 +88,13 @@ const useStyles = createStyles(({ theme: { colors } }) => ({
|
|||
button: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
alignSelf: "flex-end",
|
||||
alignSelf: "stretch",
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
backgroundColor: colors.surface,
|
||||
borderRadius: 8,
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
minHeight: 48, // Consistent with RadarButton for accessibility
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
shadowColor: colors.text,
|
||||
|
|
57
src/scenes/SendAlert/RadarButton.js
Normal file
57
src/scenes/SendAlert/RadarButton.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { IconButton } from "react-native-paper";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { createStyles } from "~/theme";
|
||||
|
||||
export default function RadarButton({ onPress, isLoading = false, flex = 0.22 }) {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { flex }]}>
|
||||
<IconButton
|
||||
accessibilityLabel="Radar - Voir les utilisateurs Alerte-Secours prêts à porter secours aux alentours"
|
||||
mode="contained"
|
||||
size={24}
|
||||
style={styles.button}
|
||||
onPress={onPress}
|
||||
disabled={isLoading}
|
||||
icon={({ size, color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="radar"
|
||||
size={size}
|
||||
color={color}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles(({ wp, hp, theme: { colors, custom } }) => ({
|
||||
container: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
flex: 1, // Stretch to fill available space
|
||||
},
|
||||
button: {
|
||||
backgroundColor: colors.primary,
|
||||
elevation: 4,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 3.84,
|
||||
minHeight: 48, // Match minimum touch target height
|
||||
width: "100%",
|
||||
maxWidth: 48, // Keep it square for radar button
|
||||
},
|
||||
icon: {
|
||||
color: colors.onPrimary,
|
||||
},
|
||||
}));
|
176
src/scenes/SendAlert/RadarModal.js
Normal file
176
src/scenes/SendAlert/RadarModal.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
import React from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { Modal, Portal, Button, ActivityIndicator } from "react-native-paper";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { createStyles, useTheme } from "~/theme";
|
||||
|
||||
export default function RadarModal({
|
||||
visible,
|
||||
onDismiss,
|
||||
peopleCount = null,
|
||||
isLoading = false,
|
||||
error = null,
|
||||
}) {
|
||||
const { colors } = useTheme();
|
||||
const styles = useStyles();
|
||||
|
||||
const renderContent = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
<Text style={styles.loadingText}>
|
||||
Recherche d'utilisateurs Alerte-Secours disponibles aux alentours...
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View style={styles.errorContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name="alert-circle"
|
||||
size={48}
|
||||
color={colors.error}
|
||||
/>
|
||||
<Text style={styles.errorTitle}>Erreur</Text>
|
||||
<Text style={styles.errorText}>
|
||||
Impossible de récupérer les informations. Vérifiez votre connexion
|
||||
et votre localisation.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (peopleCount !== null) {
|
||||
return (
|
||||
<View style={styles.successContainer}>
|
||||
<Text style={styles.countText}>{peopleCount}</Text>
|
||||
<Text style={styles.descriptionText}>
|
||||
{peopleCount === 0
|
||||
? "Aucun utilisateur d'Alerte-Secours disponible pour assistance dans un rayon de 25 km"
|
||||
: peopleCount === 1
|
||||
? "utilisateur d'Alerte-Secours prêt à porter secours dans un rayon de 25 km"
|
||||
: "utilisateurs d'Alerte-Secours prêts à porter secours dans un rayon de 25 km"}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onDismiss={onDismiss}
|
||||
contentContainerStyle={[
|
||||
styles.modalContainer,
|
||||
{ backgroundColor: colors.surface },
|
||||
]}
|
||||
>
|
||||
<View style={styles.modalHeader}>
|
||||
<MaterialCommunityIcons
|
||||
name="radar"
|
||||
size={32}
|
||||
color={colors.primary}
|
||||
style={styles.modalIcon}
|
||||
/>
|
||||
<Text style={styles.modalTitle}>Utilisateurs aux alentours</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.content}>
|
||||
{renderContent()}
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={onDismiss}
|
||||
style={styles.closeButton}
|
||||
>
|
||||
Fermer
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles(({ wp, hp, scaleText, theme: { colors } }) => ({
|
||||
modalContainer: {
|
||||
margin: wp(5),
|
||||
borderRadius: 8,
|
||||
padding: wp(5),
|
||||
elevation: 5,
|
||||
},
|
||||
content: {
|
||||
alignItems: "center",
|
||||
},
|
||||
loadingContainer: {
|
||||
alignItems: "center",
|
||||
paddingVertical: hp(3),
|
||||
},
|
||||
loadingText: {
|
||||
...scaleText({ fontSize: 16 }),
|
||||
color: colors.onSurface,
|
||||
marginTop: hp(2),
|
||||
},
|
||||
errorContainer: {
|
||||
alignItems: "center",
|
||||
paddingVertical: hp(2),
|
||||
},
|
||||
errorTitle: {
|
||||
...scaleText({ fontSize: 18 }),
|
||||
fontWeight: "bold",
|
||||
color: colors.error,
|
||||
marginTop: hp(1),
|
||||
marginBottom: hp(1),
|
||||
},
|
||||
errorText: {
|
||||
...scaleText({ fontSize: 14 }),
|
||||
color: colors.onSurface,
|
||||
textAlign: "center",
|
||||
lineHeight: 20,
|
||||
},
|
||||
successContainer: {
|
||||
alignItems: "center",
|
||||
paddingVertical: hp(2),
|
||||
},
|
||||
countText: {
|
||||
...scaleText({ fontSize: 36 }),
|
||||
fontWeight: "bold",
|
||||
color: colors.primary,
|
||||
marginTop: hp(1),
|
||||
},
|
||||
descriptionText: {
|
||||
...scaleText({ fontSize: 16 }),
|
||||
color: colors.onSurface,
|
||||
textAlign: "center",
|
||||
marginTop: hp(1),
|
||||
},
|
||||
buttonContainer: {
|
||||
marginTop: hp(3),
|
||||
width: "100%",
|
||||
},
|
||||
closeButton: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginBottom: hp(2),
|
||||
},
|
||||
modalIcon: {
|
||||
marginRight: wp(2),
|
||||
},
|
||||
modalTitle: {
|
||||
...scaleText({ fontSize: 20 }),
|
||||
fontWeight: "bold",
|
||||
color: colors.onSurface,
|
||||
textAlign: "center",
|
||||
},
|
||||
}));
|
21
src/scenes/SendAlert/TopButtonsBar.js
Normal file
21
src/scenes/SendAlert/TopButtonsBar.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { createStyles } from "~/theme";
|
||||
|
||||
export default function TopButtonsBar({ children }) {
|
||||
const styles = useStyles();
|
||||
|
||||
return <View style={styles.container}>{children}</View>;
|
||||
}
|
||||
|
||||
const useStyles = createStyles(({ wp, hp }) => ({
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
alignItems: "stretch", // Ensures both buttons have same height
|
||||
justifyContent: "space-between",
|
||||
marginTop: hp(1),
|
||||
marginBottom: hp(1),
|
||||
gap: wp(3), // Slightly more space between the buttons
|
||||
minHeight: 48, // Minimum touch target height for accessibility
|
||||
},
|
||||
}));
|
9
src/scenes/SendAlert/gql.js
Normal file
9
src/scenes/SendAlert/gql.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { gql } from "@apollo/client";
|
||||
|
||||
export const RADAR_PEOPLE_COUNT_QUERY = gql`
|
||||
query radarPeopleCount {
|
||||
getOneRadarPeopleCount {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -13,6 +13,10 @@ import HelpBlock from "./HelpBlock";
|
|||
import RegisterRelativesButton from "./RegisterRelativesButton";
|
||||
import NotificationsButton from "./NotificationsButton";
|
||||
import ContributeButton from "./ContributeButton";
|
||||
import RadarButton from "./RadarButton";
|
||||
import RadarModal from "./RadarModal";
|
||||
import TopButtonsBar from "./TopButtonsBar";
|
||||
import useRadarData from "~/hooks/useRadarData";
|
||||
|
||||
export default function SendAlert() {
|
||||
const navigation = useNavigation();
|
||||
|
@ -20,10 +24,35 @@ export default function SendAlert() {
|
|||
const styles = useStyles();
|
||||
|
||||
const [helpVisible, setHelpVisible] = useState(false);
|
||||
const [radarModalVisible, setRadarModalVisible] = useState(false);
|
||||
|
||||
const {
|
||||
data: radarData,
|
||||
isLoading: radarIsLoading,
|
||||
error: radarError,
|
||||
fetchRadarData,
|
||||
reset: resetRadarData,
|
||||
hasLocation,
|
||||
} = useRadarData();
|
||||
|
||||
function toggleHelp() {
|
||||
setHelpVisible(!helpVisible);
|
||||
}
|
||||
|
||||
const handleRadarPress = useCallback(() => {
|
||||
if (!hasLocation) {
|
||||
// Could show a location permission alert here
|
||||
return;
|
||||
}
|
||||
setRadarModalVisible(true);
|
||||
fetchRadarData();
|
||||
}, [hasLocation, fetchRadarData]);
|
||||
|
||||
const handleRadarModalClose = useCallback(() => {
|
||||
setRadarModalVisible(false);
|
||||
resetRadarData();
|
||||
}, [resetRadarData]);
|
||||
|
||||
const navigateTo = useCallback(
|
||||
(navOpts) =>
|
||||
navigation.dispatch({
|
||||
|
@ -70,7 +99,14 @@ export default function SendAlert() {
|
|||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.container}>
|
||||
<NotificationsButton />
|
||||
<TopButtonsBar>
|
||||
<NotificationsButton flex={0.78} />
|
||||
<RadarButton
|
||||
onPress={handleRadarPress}
|
||||
isLoading={radarIsLoading}
|
||||
flex={0.22}
|
||||
/>
|
||||
</TopButtonsBar>
|
||||
|
||||
<View style={styles.head}>
|
||||
<Title style={styles.title}>Quelle est votre situation ?</Title>
|
||||
|
@ -218,6 +254,14 @@ export default function SendAlert() {
|
|||
</View>
|
||||
|
||||
<ContributeButton />
|
||||
|
||||
<RadarModal
|
||||
visible={radarModalVisible}
|
||||
onDismiss={handleRadarModalClose}
|
||||
peopleCount={radarData?.count}
|
||||
isLoading={radarIsLoading}
|
||||
error={radarError}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue