Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade text2image to use stablediffusion huggingface api #28

Open
dreaminglucid opened this issue Aug 30, 2023 · 1 comment
Open

Upgrade text2image to use stablediffusion huggingface api #28

dreaminglucid opened this issue Aug 30, 2023 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@dreaminglucid
Copy link
Owner

dreaminglucid commented Aug 30, 2023

https://huggingface.co/runwayml/stable-diffusion-v1-5

https://huggingface.co/docs/api-inference/index

Ideally, we will want to use the paid Inference Endpoints https://ui.endpoints.huggingface.co/welcome

... and not the free Inference API

@dreaminglucid dreaminglucid added the enhancement New feature or request label Aug 30, 2023
@dreaminglucid dreaminglucid self-assigned this Aug 30, 2023
@dreaminglucid
Copy link
Owner Author

this could also be done on the frontend like so:

// React and React Native Libraries
import React, { useState, useEffect, useContext } from "react";
import {
    StyleSheet,
    View,
    Text,
    Alert,
    Image,
    Modal,
    Animated,
    ActivityIndicator,
    ScrollView,
    RefreshControl,
    TouchableOpacity,
} from "react-native";
import { Button, Card, Subheading } from "react-native-paper";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { ThemeContext } from '../Contexts/ThemeContext';
import * as SecureStore from 'expo-secure-store';
import * as MediaLibrary from 'expo-media-library';
import * as FileSystem from 'expo-file-system';
import { API_URL, HF_API_TOKEN, OPENAI_API_KEY } from "../../config";
import axios from 'axios';
import 'react-native-url-polyfill/auto';

const DetailsScreen = ({ route, navigation }) => {
    const { theme } = useContext(ThemeContext);
    const styles = getStyles(theme);
    let { dreamId } = route.params;
    dreamId = String(dreamId);
    const [dream, setDream] = useState(null);
    const [analysisResult, setAnalysisResult] = useState("");
    const [imageData, setImageData] = useState(null);
    const [isRefreshing, setIsRefreshing] = useState(false);
    const [isLoading, setIsLoading] = useState(false);
    const [loadingStatus, setLoadingStatus] = useState("");
    const [generationStatus, setGenerationStatus] = useState("idle");
    const [isImageModalVisible, setImageModalVisible] = useState(false);
    const [isDeleteModalVisible, setDeleteModalVisible] = useState(false);
    const animation = new Animated.Value(0);
    const [localImageURI, setLocalImageURI] = useState(null);
    const [imageStyle, setImageStyle] = useState("renaissance");
    const [imageQuality, setImageQuality] = useState("low");
    const [tempImageData, setTempImageData] = useState(null);

    // Prefetch the image for caching
    useEffect(() => {
        if (localImageURI || (imageData && imageData.startsWith('http'))) {
            const uriToPrefetch = localImageURI || imageData;
            console.log("Prefetching image from URI:", uriToPrefetch);
            Image.prefetch(uriToPrefetch).catch((error) =>
                console.error("Error prefetching image:", error)
            );
        }
    }, [imageData, localImageURI]);

    // Similar loading animation logic as in RegenerateScreen
    useEffect(() => {
        if (isLoading) {
            const animate = Animated.loop(
                Animated.sequence([
                    Animated.timing(animation, {
                        toValue: 1,
                        duration: 500,
                        useNativeDriver: true,
                    }),
                    Animated.timing(animation, {
                        toValue: 0,
                        duration: 500,
                        useNativeDriver: true,
                    }),
                ]),
            );
            animate.start();
        } else {
            animation.stopAnimation();
        }
    }, [isLoading]);

    const dot1 = animation.interpolate({
        inputRange: [0, 0.4, 0.8, 1],
        outputRange: [0, 1, 0, 0],
    });
    const dot2 = animation.interpolate({
        inputRange: [0, 0.2, 0.6, 1],
        outputRange: [0, 1, 0, 0],
    });
    const dot3 = animation.interpolate({
        inputRange: [0, 0, 0.4, 0.8, 1],
        outputRange: [0, 1, 0, 0, 0],
    });

    useEffect(() => {
        const unsubscribe = navigation.addListener("focus", fetchDream);

        // Call fetchDream for the first time
        fetchDream();

        return unsubscribe;
    }, [navigation, route.params.dreamUpdated, theme]);

    useEffect(() => {
        if (generationStatus === "generating") {
            handleGenerateDream();
        }
    }, [generationStatus]);

    const fetchDream = async () => {
        setLocalImageURI(null);
        if (route.params && route.params.dreamData) {
            let dreamData = route.params.dreamData;
            setDream(dreamData);
            if ("analysis" in dreamData && dreamData.analysis !== null) {
                let analysisText = dreamData.analysis;
                try {
                    const parsedText = JSON.parse(analysisText);
                    if (typeof parsedText === "string") {
                        analysisText = parsedText;
                    }
                } catch (e) {
                    // JSON parsing failed, use the original string
                }
                analysisText = analysisText.replace(/\\"/g, '"').replace(/\\n/g, "\n");
                setAnalysisResult(analysisText);
            }
            const localURI = await fetchLocalImageURI();
            if (localURI) {
                setLocalImageURI(localURI);
                console.log("Image loaded from local URI:", localURI);
            } else if (imageData) {
                console.log("Image loaded from server:", imageData);
            }
            setIsRefreshing(false);
        } else {
            try {
                setIsRefreshing(true);
                const userJson = await SecureStore.getItemAsync('appleUser');
                const user = JSON.parse(userJson);

                const response = await fetch(`${API_URL}/api/dreams/${dreamId}`, {
                    headers: {
                        "Authorization": `Bearer ${user.id_token}`,
                    },
                });

                if (response.ok) {
                    let dreamData = await response.json();
                    if ("analysis" in dreamData && dreamData.analysis !== null) {
                        let analysisText = dreamData.analysis;
                        try {
                            const parsedText = JSON.parse(analysisText);
                            if (typeof parsedText === "string") {
                                analysisText = parsedText;
                            }
                        } catch (e) {
                            // JSON parsing failed, use the original string
                        }
                        analysisText = analysisText.replace(/\\"/g, '"').replace(/\\n/g, "\n");
                        setAnalysisResult(analysisText);
                    }
                    if ("image" in dreamData) {
                        setImageData(dreamData.image);
                    }
                    setDream(dreamData);
                } else {
                    Alert.alert("Error", "Failed to fetch dream details.");
                }
            } catch (error) {
                console.error("Error:", error);
                Alert.alert("Error", "An unexpected error occurred.");
            } finally {
                setIsRefreshing(false);
            }
        }
    };

    async function getDreamSummary(dreamEntry) {
        console.log("Entering getDreamSummary...");
        const prompt = `Awaken to the depths of your subconscious, where dreams transcend reality. Describe the enigmatic tale of your nocturnal journey, where the ethereal dance of ${dreamEntry} beguiles the senses. Condense this profound experience into a succinct prompt, grounding the essence of your dream in the realms of research, literature, science, mysticism, and ancient wisdom. This prompt will guide the DALLE AI image generation tool by OpenAI, all in under 100 characters.`;
        const systemContent = "You are a helpful assistant that will summarize the user's dream entry.";
        console.log(`Using prompt: ${prompt}`);

        try {
            console.log("Sending request to OpenAI API...");
            const response = await axios.post(
                'https://api.openai.com/v1/chat/completions', {
                model: 'gpt-3.5-turbo',
                messages: [
                    {
                        role: 'system',
                        content: systemContent
                    },
                    {
                        role: 'user',
                        content: prompt
                    }
                ],
                temperature: 0.9,
                max_tokens: 100
            },
                {
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${OPENAI_API_KEY}`
                    }
                }
            );
            console.log("Received response from OpenAI API.");
            const aiResponse = response.data.choices[0]?.message?.content?.trim();
            if (aiResponse) {
                console.log("Successfully generated summary.");
                return aiResponse;
            } else {
                console.error("GPT-3 summary generation failed: No choices in response.");
                return null;
            }
        } catch (error) {
            console.error(`Error in getDreamSummary: ${error}`);
            return null;
        }
    }

    const handleGenerateDream = async () => {
        console.log("Entering handleGenerateDream...");
        if (generationStatus === "generating") {
            console.log("Generation already in progress. Exiting...");
            return;
        }
        console.log("Setting generationStatus to 'generating'.");
        setGenerationStatus("generating");
        setIsLoading(true);
        setLoadingStatus("Generating Analysis & Image");

        try {
            console.log("Generating dream summary...");
            const summary = await getDreamSummary(dream.metadata.entry);
            console.log(`Generated summary: ${summary}`);

            let styleDescription = "A renaissance painting of";
            if (imageStyle === "abstract") {
                styleDescription = "An abstract representation of";
            } else if (imageStyle === "modern") {
                styleDescription = "A modern artwork of";
            }

            const qualityResolutionMap = {
                "low": "256x256",
                "medium": "512x512",
                "high": "1024x1024",
            };
            const resolution = qualityResolutionMap[imageQuality];

            const prompt = `${styleDescription} ${summary}, high quality, lucid dream themed.`;

            console.log("Generating dream image...");
            const generatedImage = await query({
                "inputs": prompt,
                "size": resolution,
            });

            console.log("Successfully generated dream image.");

            const generatedImageUrl = URL.createObjectURL(generatedImage);
            console.log(`Generated image URL: ${generatedImageUrl}`);

            console.log("Fetching dream analysis...");
            const analysis = await fetchDreamAnalysis();
            console.log("Successfully fetched dream analysis.");

            setAnalysisResult(analysis);
            setTempImageData(generatedImageUrl);
            setIsLoading(false);
            setLoadingStatus("");
            setGenerationStatus("success");
        } catch (error) {
            console.error(`Error during dream generation: ${error}`);
            Alert.alert("Error", "An unexpected error occurred during dream generation.");
            setIsLoading(false);
            setLoadingStatus("");
            setGenerationStatus("idle");
        }
    };

    useEffect(() => {
        console.log("Entering useEffect for checking analysisResult and imageData...");
        if (analysisResult && imageData) {
            console.log("Both analysisResult and imageData are available. Setting isLoading to false and generationStatus to 'success'.");
            setIsLoading(false);
            setGenerationStatus("success");
        }
    }, [analysisResult, imageData]);

    const fetchDreamAnalysis = async () => {
        try {
            const userJson = await SecureStore.getItemAsync('appleUser');
            const user = JSON.parse(userJson);

            const response = await fetch(`${API_URL}/api/dreams/${dreamId}/analysis`, {
                headers: {
                    "Authorization": `Bearer ${user.id_token}`,
                },
            });

            if (!response.ok) {
                throw new Error("Failed to fetch dream analysis.");
            }
            let analysisResult = await response.text();
            try {
                // Try to parse the string as JSON
                const parsedText = JSON.parse(analysisResult);
                if (typeof parsedText === "string") {
                    // If the parsed result is a string, use it
                    analysisResult = parsedText;
                }
            } catch (e) {
                // If parsing fails, it's not valid JSON, so we'll just use the original string
            }
            analysisResult = analysisResult
                .replace(/\\"/g, '"')
                .replace(/\\n/g, "\n");
            return analysisResult;
        } catch (error) {
            console.error("Error:", error);
            Alert.alert("Error", "An unexpected error occurred.");
            throw error; // Throw the error so it can be caught in handleGenerateDream
        }
    };

    // const fetchDreamImage = async () => {
    //     try {
    //         const userJson = await SecureStore.getItemAsync('appleUser');
    //         const user = JSON.parse(userJson);

    //         const response = await fetch(`${API_URL}/api/dreams/${dreamId}/image`, {
    //             headers: {
    //                 "Authorization": `Bearer ${user.id_token}`,
    //             },
    //         });

    //         if (response.ok) {
    //             const imageData = await response.json();
    //             return imageData.image;
    //         } else {
    //             Alert.alert("Error", "Failed to fetch dream image.");
    //         }
    //     } catch (error) {
    //         console.error("Error:", error);
    //         Alert.alert("Error", "An unexpected error occurred.");
    //     }
    // };

    // Generate image using Hugging Face API
    async function query(data) {
        console.log("Entering query function...");
        console.log(`Data to Hugging Face API: ${JSON.stringify(data)}`);
        try {
            const response = await fetch(
                "https://api-inference.huggingface.co/models/runwayml/stable-diffusion-v1-5",
                {
                    headers: { Authorization: `Bearer ${HF_API_TOKEN}` },
                    method: "POST",
                    body: JSON.stringify(data),
                }
            );
            console.log("Received response from Hugging Face API.");
            const result = await response.blob();
            console.log("Successfully converted response to blob.");
            return result;
        } catch (error) {
            console.error(`Error in query function: ${error}`);
            return null;
        }
    };

    const updateLocalImageStyle = (newStyle) => {
        console.log(`Updating local image style to ${newStyle}...`);
        setImageStyle(newStyle);
        console.log("Local image style updated.");
    };

    const updateLocalImageQuality = (newQuality) => {
        console.log(`Updating local image quality to ${newQuality}...`);
        setImageQuality(newQuality);
        console.log("Local image quality updated.");
    };

    const handleSaveAnalysisAndImage = async () => {
        console.log("Entering handleSaveAnalysisAndImage...");
        try {
            console.log("Fetching user data...");
            const userJson = await SecureStore.getItemAsync('appleUser');
            const user = JSON.parse(userJson);
            console.log("Successfully fetched user data.");

            console.log("Sending PUT request to update dream analysis and image...");
            const response = await fetch(`${API_URL}/api/dreams/${dreamId}`, {
                method: "PUT",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${user.id_token}`,
                },
                body: JSON.stringify({ analysis: analysisResult, image: imageData }),
            });

            if (response.ok) {
                console.log("PUT request successful.");
                Alert.alert("Success", "Analysis and image saved successfully!");
                setDream({
                    ...dream,
                    analysis: analysisResult,
                    image: imageData,
                });

                console.log("Saving image to device's media library...");
                saveImageToLibrary(imageData);
                console.log("Image saved to device's media library.");
            } else {
                console.log("PUT request failed.");
                Alert.alert("Error", "Failed to save analysis and image.");
            }
        } catch (error) {
            console.error(`Error in handleSaveAnalysisAndImage: ${error}`);
            Alert.alert("Error", "An unexpected error occurred.");
        }
    };

    const saveImageToLibrary = async (imageURI) => {
        try {
            // Ensure permissions
            const { status } = await MediaLibrary.requestPermissionsAsync();
            if (status !== 'granted') {
                Alert.alert('Permission Denied', 'Unable to access media library.');
                return;
            }

            // Download if it's a URL
            let localUri = imageURI;
            if (imageURI.startsWith('http')) {
                const { uri } = await FileSystem.downloadAsync(imageURI, FileSystem.documentDirectory + `image_${dreamId}.jpg`);
                localUri = uri;
            }

            // Save to media library
            const asset = await MediaLibrary.createAssetAsync(localUri);

            // Check "Dreams" album
            let album = await MediaLibrary.getAlbumAsync('Dreams');
            if (album === null) {
                album = await MediaLibrary.createAlbumAsync('Dreams', asset, false);
            } else {
                await MediaLibrary.addAssetsToAlbumAsync([asset], album, false);
            }

            // Update the local image URI
            setLocalImageURI(localUri);

            Alert.alert('Success', 'Image saved to "Dreams" album!');
        } catch (error) {
            console.error('Error saving image:', error);
            Alert.alert('Error', 'Failed to save image to library.');
        }
    };

    const fetchLocalImageURI = async () => {
        try {
            // Check for the file by dream ID in the local file system
            const fileUri = FileSystem.documentDirectory + `image_${dreamId}.jpg`;
            const fileInfo = await FileSystem.getInfoAsync(fileUri);
            if (fileInfo.exists) {
                return fileUri;
            }
        } catch (error) {
            console.error('Error fetching local image:', error);
        }
        return null;
    };

    // Function to handle opening the delete modal
    const handleOpenDeleteModal = () => {
        setDeleteModalVisible(true);
    };

    // Function to handle closing the delete modal
    const handleCloseDeleteModal = () => {
        setDeleteModalVisible(false);
    };

    const handleCloseImageModal = () => {
        setImageModalVisible(false);
    };

    const handleOpenImageModal = () => {
        setImageModalVisible(true);
    };

    // Function to delete a dream
    const deleteDream = async () => {
        const userJson = await SecureStore.getItemAsync('appleUser');
        const user = JSON.parse(userJson);
        try {
            const response = await fetch(`${API_URL}/api/dreams/${dreamId}`, {
                method: "DELETE",
                headers: {
                    'Authorization': `Bearer ${user.id_token}`,
                },
            });

            if (response.ok) {
                Alert.alert("Success", "Dream deleted successfully.");
                navigation.goBack(); // Navigate back after deletion
            } else {
                Alert.alert("Error", "Failed to delete the dream.");
            }
        } catch (error) {
            Alert.alert("Error", "Failed to delete the dream.");
        }
    };

    // Function to confirm deletion
    const confirmDeleteDream = () => {
        Alert.alert(
            "Delete Dream",
            "Are you sure you want to delete this dream?",
            [
                { text: "Cancel", style: "cancel" },
                { text: "Delete", onPress: deleteDream },
            ]
        );
    };

    const renderImage = () => {
        let imageSource = localImageURI || imageData;
        if (imageSource) {
            return (
                <TouchableOpacity onPress={handleOpenImageModal} style={styles.imageContainer}>
                    <Image
                        source={{ uri: imageSource }}
                        style={styles.image}
                        resizeMode="contain"
                        onError={(e) => {
                            console.error("Error loading image:", e.nativeEvent.error);
                        }}
                    />
                </TouchableOpacity>
            );
        } else {
            return (
                <View style={styles.imagePlaceholder}>
                    <MaterialCommunityIcons name="image-off" size={48} color="#aaa" />
                </View>
            );
        }
    };

    return (
        <ScrollView
            contentContainerStyle={styles.container}
            refreshControl={
                <RefreshControl refreshing={isRefreshing} onRefresh={fetchDream} />
            }
        >
            <Modal
                animationType="slide"
                transparent={true}
                visible={isImageModalVisible}
                onRequestClose={handleCloseImageModal}
            >
                <TouchableOpacity onPress={handleCloseImageModal} style={styles.expandedImageContainer}>
                    <Image source={{ uri: localImageURI || imageData }} style={styles.expandedImage} resizeMode="contain" />
                </TouchableOpacity>
            </Modal>
            {isLoading ? (
                <View style={styles.loadingContainer}>
                    <ActivityIndicator size="large" color={theme.colors.button} />
                    <View style={styles.loadingMessageContainer}>
                        <Text style={styles.loadingMessage}>{loadingStatus}</Text>
                        <Animated.Text style={[styles.loadingDot, { opacity: dot1 }]}>.</Animated.Text>
                        <Animated.Text style={[styles.loadingDot, { opacity: dot2 }]}>.</Animated.Text>
                        <Animated.Text style={[styles.loadingDot, { opacity: dot3 }]}>.</Animated.Text>
                    </View>
                </View>
            ) : (
                <>
                    {renderImage()}
                    <View style={styles.detailsContainer}>
                        {dream && (
                            <>
                                <Card style={styles.card}>
                                    <Card.Content>
                                        <View style={[styles.infoBlock, { flexDirection: 'column' }]}>
                                            <View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
                                                <MaterialCommunityIcons name="book" color={theme.colors.button} size={24} />
                                                <Subheading style={[styles.subLabel, { color: theme.colors.button, marginLeft: 10 }]}>Dream Title</Subheading>
                                            </View>
                                            <Text style={styles.dreamTitle}>{dream.metadata.title}</Text>
                                        </View>
                                    </Card.Content>
                                </Card>

                                <Card style={styles.card}>
                                    <Card.Content>
                                        <View style={[styles.infoBlock, { flexDirection: 'column' }]}>
                                            <View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
                                                <MaterialCommunityIcons name="calendar" color={theme.colors.button} size={24} />
                                                <Subheading style={[styles.subLabel, { color: theme.colors.button, marginLeft: 10 }]}>Dream Date</Subheading>
                                            </View>
                                            <Text style={styles.dreamDate}>{dream.metadata.date}</Text>
                                        </View>
                                    </Card.Content>
                                </Card>

                                <Card style={styles.card}>
                                    <Card.Content>
                                        <View style={[styles.infoBlock, { flexDirection: 'column' }]}>
                                            <View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
                                                <MaterialCommunityIcons name="note-text" color={theme.colors.button} size={24} />
                                                <Subheading style={[styles.subLabel, { color: theme.colors.button, marginLeft: 10 }]}>Dream Entry</Subheading>
                                            </View>
                                            <Text style={styles.dreamEntry}>{dream.metadata.entry}</Text>
                                        </View>
                                    </Card.Content>
                                </Card>

                                <Card style={styles.card}>
                                    <Card.Content>
                                        <View style={[styles.infoBlock, { flexDirection: 'column' }]}>
                                            <View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
                                                <MaterialCommunityIcons name="brain" color={theme.colors.button} size={24} />
                                                <Subheading style={[styles.analysisLabel, { color: theme.colors.button, marginLeft: 10 }]}>Dream Analysis</Subheading>
                                            </View>
                                            {analysisResult ? (
                                                <Text style={styles.analysisResult}>{analysisResult}</Text>
                                            ) : (
                                                <Text style={styles.analysisPlaceholder}>No analysis generated</Text>
                                            )}
                                        </View>
                                    </Card.Content>
                                </Card>
                            </>
                        )}
                        <View style={styles.buttonContainer}>
                            {analysisResult && imageData && !(dream && dream.analysis && dream.image) && (
                                <Button
                                    mode="contained"
                                    onPress={handleSaveAnalysisAndImage}
                                    style={styles.saveButton}
                                    labelStyle={styles.saveButtonText}
                                >
                                    Save
                                </Button>
                            )}
                        </View>
                        {/* Delete Button */}
                        <TouchableOpacity onPress={handleOpenDeleteModal} style={styles.deleteButtonContainer}>
                            <MaterialCommunityIcons name="pencil" size={24} color={theme.colors.background} />
                        </TouchableOpacity>

                        {/* Delete Modal */}
                        <Modal
                            animationType="slide"
                            transparent={true}
                            visible={isDeleteModalVisible}
                            onRequestClose={handleCloseDeleteModal}
                        >
                            <View style={styles.deleteModalContainer}>
                                {dream && dream.analysis && dream.image ? (
                                    <Button
                                        mode="contained"
                                        onPress={() => {
                                            navigation.navigate("Regenerate", {
                                                dreamId,
                                                dreamData: dream,
                                            });
                                            handleCloseDeleteModal(); // Close modal after navigating
                                        }}
                                        style={styles.generateButton}
                                        labelStyle={styles.generateButtonText}
                                    >
                                        Edit
                                    </Button>
                                ) : (
                                    <Button
                                        mode="contained"
                                        onPress={() => {
                                            handleGenerateDream();
                                            handleCloseDeleteModal(); // Close modal after generating
                                        }}
                                        style={styles.generateButton}
                                        labelStyle={styles.generateButtonText}
                                    >
                                        Generate
                                    </Button>
                                )}
                                <Button
                                    mode="contained"
                                    onPress={confirmDeleteDream}
                                    style={[styles.deleteModalButton, styles.deleteModalDeleteButton]} // Added specific style for delete button
                                    labelStyle={styles.deleteModalButtonText}
                                >
                                    Delete
                                </Button>
                                <Button onPress={handleCloseDeleteModal} style={styles.cancelModalButton}>
                                    Cancel
                                </Button>
                            </View>
                        </Modal>
                    </View>
                </>
            )}
        </ScrollView>
    );
};

const getStyles = (theme) => StyleSheet.create({
    container: {
        flexGrow: 1,
        backgroundColor: theme.colors.background,
    },
    detailsContainer: {
        borderTopLeftRadius: 30,
        borderTopRightRadius: 30,
        backgroundColor: theme.colors.background,
        padding: 20,
        paddingTop: 40,
        marginTop: -30,
    },
    card: {
        backgroundColor: theme.colors.card,
        marginBottom: 20,
        borderRadius: 22,
        borderColor: "#123",
        shadowColor: "#000",
        shadowOffset: {
            width: 0,
            height: 1,
        },
        shadowOpacity: 0.26,
        shadowRadius: 2,
        elevation: 4,
    },
    infoBlock: {
        flexDirection: 'row',
        marginBottom: 0,
    },
    subLabel: {
        fontSize: 18,
        fontWeight: "bold",
        marginBottom: 5,
        color: theme.colors.primary,
    },
    dreamTitle: {
        fontSize: 20,
        marginBottom: 10,
        color: theme.colors.text,
        fontWeight: "bold",
    },
    dreamDate: {
        fontSize: 18,
        marginBottom: 10,
        color: theme.colors.text,
        fontWeight: "bold",
    },
    dreamEntry: {
        fontSize: 18,
        marginBottom: 10,
        color: theme.colors.text,
    },
    imagePlaceholder: {
        width: "100%",
        height: 375,
        backgroundColor: "#ddd",
        alignItems: 'center',
        justifyContent: 'center',
    },
    analysisPlaceholder: {
        fontSize: 18,
        color: "#aaa",
    },
    analysisLabel: {
        fontSize: 20,
        fontWeight: "bold",
        marginBottom: 5,
        color: theme.colors.primary,
    },
    analysisResult: {
        fontSize: 18,
        marginBottom: 10,
        color: theme.colors.text,
    },
    loadingIndicator: {
        justifyContent: "center",
        height: "90%",
    },
    imageContainer: {
        width: "100%",
        height: 375,
        backgroundColor: theme.colors.primary,
        overflow: 'hidden',
    },
    expandedImageContainer: {
        flex: 1,
        backgroundColor: 'rgba(0, 0, 0, 0.9)',
        justifyContent: 'center',
        alignItems: 'center',
        padding: 20,
    },
    expandedImage: {
        width: '100%',
        height: '100%',
        maxWidth: '100%',
        maxHeight: '100%',
        margin: 20,
    },
    image: {
        width: '100%',
        height: 390,
    },
    buttonContainer: {
        marginBottom: 20,
    },
    deleteModalContainer: {
        position: 'absolute',
        bottom: 0,
        left: 0,
        right: 0,
        backgroundColor: theme.colors.card,
        padding: 20,
        borderTopLeftRadius: 30,
        borderTopRightRadius: 30,
    },
    deleteModalButton: {
        borderRadius: 50,
        marginBottom: 10,
        backgroundColor: 'red',
    },
    deleteModalButtonText: {
        color: theme.colors.background,
        fontWeight: "bold",
        fontSize: 18,
    },
    deleteButtonContainer: {
        position: 'absolute',
        right: 35, // Adjust this to change the button's horizontal position
        top: -20, // Adjust this to change the button's vertical position
        zIndex: 2,
        width: 50, // Diameter of the button
        height: 50, // Diameter of the button
        borderRadius: 25, // Half the diameter to make it circular
        backgroundColor: theme.colors.button, // Match the background color of the button
        justifyContent: 'center',
        alignItems: 'center',
        shadowColor: "#000",
        shadowOffset: { width: 0, height: 3 },
        shadowOpacity: 0.33,
        shadowRadius: 3,
        elevation: 6,
    },
    cancelModalButton: {
        textAlign: 'center',
        color: theme.colors.button,
    },
    generateButton: {
        marginBottom: 15,
        borderRadius: 50,
        shadowColor: "#000",
        shadowOffset: { width: 0, height: 3 },
        shadowOpacity: 0.33,
        shadowRadius: 3,
        elevation: 6,
        backgroundColor: theme.colors.button,
        width: "100%",
    },
    generateButtonText: {
        color: theme.colors.background,
        fontWeight: "bold",
        fontSize: 18,
    },
    saveButton: {
        marginBottom: 15,
        borderRadius: 50,
        shadowColor: "#000",
        shadowOffset: { width: 0, height: 3 },
        shadowOpacity: 0.33,
        shadowRadius: 3,
        elevation: 6,
        backgroundColor: theme.colors.button,
        width: "100%",
    },
    saveButtonText: {
        color: theme.colors.background,
        fontWeight: "bold",
        fontSize: 18,
    },
    loadingContainer: {
        position: "absolute",
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
        alignItems: "center",
        justifyContent: "center",
        backgroundColor: "rgba(0, 0, 0, 0.3)",
        zIndex: 1,
    },
    loadingMessageContainer: {
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "center",
        marginTop: 33,
    },
    loadingMessage: {
        fontSize: 20,
        color: theme.colors.text,
    },
    loadingDots: {
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "center",
        marginTop: 10,
    },
    loadingDot: {
        color: theme.colors.button,
        fontSize: 20,
        marginHorizontal: 5,
    },
});

export default DetailsScreen;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant