Skip to content
35 changes: 6 additions & 29 deletions ecoscan_app/app/(tabs)/(scan)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useRef, useState } from "react";
import {
KeyboardAvoidingView,
Platform,
Expand All @@ -7,7 +7,7 @@ import {
TextInput,
View,
} from "react-native";
import { ActivityIndicator, Button, Snackbar, Text } from "react-native-paper";
import { ActivityIndicator, Button, Text } from "react-native-paper";

import BarcodeScanner from "@/components/BarcodeScanner";
import { PageContainer } from "@/components/PageContainer";
Expand All @@ -18,25 +18,18 @@ import { useError } from "@/context/ErrorContext";

export default function Scan() {
const [barcode, setBarcode] = useState("");
const [error, setError] = useState<string | undefined>();
const [snackbarVisible, setSnackbarVisible] = useState(false);
const { setError } = useError();
const router = useRouter();
const { loading, analyzeProduct, cancelAnalysis } = useAnalyzeProduct();
const { consumeError } = useError();
const [scanned, setScanned] = useState(false);
const scrollViewRef = useRef<ScrollView>(null);

const showError = (message: string) => {
setError(message);
setSnackbarVisible(true);
};

const onScanned = async (code: string) => {
scrollViewRef.current?.scrollTo({ y: 0, animated: true });
setScanned(true);
const trimmed = code.trim();
if (!trimmed) {
showError("Barcode darf nicht leer sein.");
setError("Barcode darf nicht leer sein.");
return;
}
try {
Expand All @@ -47,22 +40,15 @@ export default function Scan() {
params: { id: trimmed },
});
} else {
showError("Produkt konnte nicht analysiert werden.");
setError("Produkt konnte nicht analysiert werden.");
}
} catch (err) {
const msg =
err instanceof Error ? err.message : "Analyse fehlgeschlagen.";
showError(msg);
setError(msg);
}
};

useEffect(() => {
const errorMsg = consumeError();
if (errorMsg) {
showError(errorMsg);
}
}, [consumeError]);

return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
Expand Down Expand Up @@ -136,15 +122,6 @@ export default function Scan() {
</View>
</View>
)}

<Snackbar
visible={snackbarVisible}
onDismiss={() => setSnackbarVisible(false)}
duration={4000}
style={{ backgroundColor: theme.colors.error }}
>
{error}
</Snackbar>
</PageContainer>
</ScrollView>
</KeyboardAvoidingView>
Expand Down
38 changes: 30 additions & 8 deletions ecoscan_app/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import {
useRouter,
useSegments,
} from "expo-router";
import { PaperProvider } from "react-native-paper";
import { PaperProvider, Snackbar } from "react-native-paper";
import { theme } from "@/theme";
import { AuthProvider, useAuth } from "@/context/AuthContext";
import { useEffect } from "react";
import { ErrorProvider } from "@/context/ErrorContext";
import {
useSnackbar,
getSnackbarStyles,
SnackbarProvider,
} from "@/context/SnackbarContext";
import { NotificationProvider } from "@/context/NotificationProvider";
import { ProductProvider } from "@/context/ProductContext";

Expand All @@ -17,6 +21,7 @@ function RootLayoutNav() {
const segments = useSegments();
const router = useRouter();
const navigationState = useRootNavigationState();
const { currentSnackbar, dismissSnackbar } = useSnackbar();

useEffect(() => {
if (!navigationState?.key || isLoading) return;
Expand Down Expand Up @@ -44,23 +49,40 @@ function RootLayoutNav() {
</Stack>
);

return isAuthenticated && !isLoading ? (
<NotificationProvider>{stack}</NotificationProvider>
) : (
stack
return (
<>
{isAuthenticated && !isLoading ? (
<NotificationProvider>{stack}</NotificationProvider>
) : (
stack
)}
{currentSnackbar && (
<Snackbar
visible={!!currentSnackbar}
onDismiss={dismissSnackbar}
duration={40000}
style={{
...getSnackbarStyles(currentSnackbar.type),
marginBottom: 70,
}}
>
{currentSnackbar.message}
</Snackbar>
)}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
</>
);
}

export default function RootLayout() {
return (
<PaperProvider theme={theme}>
<ErrorProvider>
<SnackbarProvider>
<ProductProvider>
<AuthProvider>
<RootLayoutNav />
</AuthProvider>
</ProductProvider>
</ErrorProvider>
</SnackbarProvider>
</PaperProvider>
);
}
29 changes: 9 additions & 20 deletions ecoscan_app/components/product/BoughtButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Button, Snackbar, Portal } from "react-native-paper";
import { Button } from "react-native-paper";
import { StyleSheet } from "react-native";
import { Product } from "@/types/product";
import { useSaveProduct } from "@/hooks/useSaveProduct";
import { useState, useEffect } from "react";
import { theme } from "@/theme";
import { useEffect } from "react";
import { useSnackbar } from "@/context/SnackbarContext";

export interface BoughtButtonProps {
product?: Product;
Expand All @@ -12,13 +12,15 @@ export interface BoughtButtonProps {
export default function BoughtButton({ product }: BoughtButtonProps) {
const { saveProduct, loading, error, success, saved, resetSaved } =
useSaveProduct();
const [visible, setVisible] = useState(false);
const { showSuccess, showError } = useSnackbar();

useEffect(() => {
if (success || error) {
setVisible(true);
if (success) {
showSuccess("Produkt erfolgreich gekauft!");
} else if (error) {
showError("Fehler beim Speichern des Produkts.");
}
}, [success, error]);
}, [success, error, showSuccess, showError]);

useEffect(() => {
resetSaved();
Expand All @@ -43,19 +45,6 @@ export default function BoughtButton({ product }: BoughtButtonProps) {
>
Gekauft
</Button>

<Portal>
<Snackbar
visible={visible}
onDismiss={() => setVisible(false)}
duration={2000}
style={{
backgroundColor: error ? theme.colors.error : theme.colors.primary,
}}
>
{error ? `${error}` : "Erfolgreich gespeichert"}
</Snackbar>
</Portal>
</>
);
}
Expand Down
40 changes: 11 additions & 29 deletions ecoscan_app/context/ErrorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,19 @@ import {
useCallback,
useContext,
useRef,
useEffect,
useState,
} from "react";
import { useSnackbar } from "@/context/SnackbarContext";
export function useError() {
const { showError } = useSnackbar();

type ErrorContextType = {
setError: (msg: string) => void;
consumeError: () => string | undefined;
};

const ErrorContext = createContext<ErrorContextType | undefined>(undefined);

export function ErrorProvider({ children }: { children: ReactNode }) {
const errorRef = useRef<string | undefined>(undefined);

const setError = useCallback((msg: string) => {
errorRef.current = msg;
}, []);

const consumeError = useCallback(() => {
const msg = errorRef.current;
errorRef.current = undefined;
return msg;
}, []);

return (
<ErrorContext.Provider value={{ setError, consumeError }}>
{children}
</ErrorContext.Provider>
const setError = useCallback(
(msg: string, duration?: number) => {
showError(msg, duration ?? 5000);
},
[showError],
);
}

export function useError() {
const ctx = useContext(ErrorContext);
if (!ctx) throw new Error("useError must be used within ErrorProvider");
return ctx;
return { setError };
}
Loading
Loading