import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, Text, Modal, TouchableOpacity, FlatList, Image, StyleSheet, Dimensions, Animated, ScrollView, ActivityIndicator, TextInput, Alert } from 'react-native'; import Slider from '@react-native-community/slider'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { X, Check, Eye, Sliders, RefreshCw, Save, Film, Zap, Sun, Contrast, Droplet, Aperture, Layers, Edit2, Trash2, ChevronLeft, Plus, Sparkles, Lightbulb, Cpu, Camera, Wand2, Palette, Clock, Star } from 'lucide-react'; const { width, height } = Dimensions.get('window'); // Define style presets with their properties const STYLE_PRESETS = [ { id: 'original', name: 'Original', description: 'No filters applied', icon: , properties: { brightness: 1, contrast: 1, saturation: 1, blur: 0, sharpen: 0, sepia: 0, grayscale: 0, hueRotate: 0, vignette: 0, }, cssFilter: '', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['neutral', 'clean', 'default'] }, { id: 'cinematic', name: 'Cinematic', description: 'Professional film look with rich colors', icon: , properties: { brightness: 0.9, contrast: 1.2, saturation: 1.1, blur: 0, sharpen: 0.2, sepia: 0.1, grayscale: 0, hueRotate: 0, vignette: 0.2, }, cssFilter: 'brightness(0.9) contrast(1.2) saturate(1.1) sepia(0.1)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['film', 'movie', 'professional', 'dramatic', 'indoor', 'portrait'] }, { id: 'vibrant', name: 'Vibrant', description: 'Bright and colorful enhancement', icon: , properties: { brightness: 1.1, contrast: 1.3, saturation: 1.5, blur: 0, sharpen: 0.1, sepia: 0, grayscale: 0, hueRotate: 0, vignette: 0, }, cssFilter: 'brightness(1.1) contrast(1.3) saturate(1.5)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['colorful', 'bright', 'outdoor', 'nature', 'food', 'celebration', 'party'] }, { id: 'blackAndWhite', name: 'Black & White', description: 'Classic monochrome look', icon: , properties: { brightness: 1, contrast: 1.2, saturation: 0, blur: 0, sharpen: 0.1, sepia: 0, grayscale: 1, hueRotate: 0, vignette: 0.1, }, cssFilter: 'grayscale(1) contrast(1.2)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['monochrome', 'classic', 'timeless', 'portrait', 'urban', 'architecture'] }, { id: 'vintage', name: 'Vintage', description: 'Nostalgic retro film effect', icon: , properties: { brightness: 0.9, contrast: 0.9, saturation: 0.8, blur: 0, sharpen: 0, sepia: 0.5, grayscale: 0.1, hueRotate: 10, vignette: 0.3, }, cssFilter: 'brightness(0.9) contrast(0.9) saturate(0.8) sepia(0.5) grayscale(0.1) hue-rotate(10deg)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['retro', 'nostalgic', 'old', 'sepia', 'history', 'antique'] }, { id: 'warm', name: 'Warm', description: 'Cozy warm tones', icon: , properties: { brightness: 1.05, contrast: 1.1, saturation: 1.2, blur: 0, sharpen: 0, sepia: 0.3, grayscale: 0, hueRotate: 5, vignette: 0.1, }, cssFilter: 'brightness(1.05) contrast(1.1) saturate(1.2) sepia(0.3) hue-rotate(5deg)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['sunset', 'golden hour', 'cozy', 'autumn', 'indoor', 'portrait', 'food'] }, { id: 'cool', name: 'Cool', description: 'Fresh cool tones', icon: , properties: { brightness: 1, contrast: 1.1, saturation: 0.9, blur: 0, sharpen: 0, sepia: 0, grayscale: 0.1, hueRotate: -10, vignette: 0.1, }, cssFilter: 'brightness(1) contrast(1.1) saturate(0.9) grayscale(0.1) hue-rotate(-10deg)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['blue', 'winter', 'water', 'night', 'urban', 'technology'] }, { id: 'dramatic', name: 'Dramatic', description: 'High contrast cinematic look', icon: , properties: { brightness: 0.85, contrast: 1.5, saturation: 0.9, blur: 0, sharpen: 0.3, sepia: 0.1, grayscale: 0.1, hueRotate: 0, vignette: 0.4, }, cssFilter: 'brightness(0.85) contrast(1.5) saturate(0.9) sepia(0.1) grayscale(0.1)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['moody', 'intense', 'dark', 'shadow', 'portrait', 'urban', 'night'] }, { id: 'dreamy', name: 'Dreamy', description: 'Soft ethereal glow effect', icon: , properties: { brightness: 1.1, contrast: 0.9, saturation: 0.95, blur: 1.5, sharpen: 0, sepia: 0.1, grayscale: 0, hueRotate: 0, vignette: 0.15, }, cssFilter: 'brightness(1.1) contrast(0.9) saturate(0.95) blur(1.5px) sepia(0.1)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['soft', 'ethereal', 'romantic', 'portrait', 'nature', 'light'] }, { id: 'afroSunset', name: 'Afro Sunset', description: 'Rich golden tones inspired by African sunsets', icon: , properties: { brightness: 1.05, contrast: 1.15, saturation: 1.3, blur: 0, sharpen: 0.1, sepia: 0.4, grayscale: 0, hueRotate: 15, vignette: 0.2, }, cssFilter: 'brightness(1.05) contrast(1.15) saturate(1.3) sepia(0.4) hue-rotate(15deg)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true, tags: ['sunset', 'africa', 'golden', 'warm', 'outdoor', 'nature', 'landscape'] } ]; // Function to generate CSS filter string from properties const generateCssFilter = (properties) => { return ` brightness(${properties.brightness}) contrast(${properties.contrast}) saturate(${properties.saturation}) blur(${properties.blur}px) sepia(${properties.sepia}) grayscale(${properties.grayscale}) hue-rotate(${properties.hueRotate}deg) `; }; // AI-powered name suggestion system const generateStyleNameSuggestions = (properties, existingName = null) => { // This is a simplified AI model that analyzes properties and suggests names // In a real implementation, this would call an API or use a more sophisticated model const suggestions = []; let baseDescription = ''; // Analyze brightness if (properties.brightness < 0.8) { baseDescription += 'dark, moody, '; } else if (properties.brightness > 1.2) { baseDescription += 'bright, airy, '; } // Analyze contrast if (properties.contrast > 1.3) { baseDescription += 'high-contrast, punchy, '; } else if (properties.contrast < 0.9) { baseDescription += 'soft, gentle, '; } // Analyze saturation if (properties.saturation > 1.3) { baseDescription += 'vibrant, colorful, '; } else if (properties.saturation < 0.7) { baseDescription += 'muted, subtle, '; } // Analyze sepia if (properties.sepia > 0.3) { baseDescription += 'vintage, nostalgic, '; } // Analyze grayscale if (properties.grayscale > 0.5) { baseDescription += 'monochrome, classic, '; } // Analyze hue rotation if (Math.abs(properties.hueRotate) > 30) { baseDescription += 'surreal, artistic, '; } // Analyze blur if (properties.blur > 2) { baseDescription += 'dreamy, ethereal, '; } // Analyze vignette if (properties.vignette > 0.3) { baseDescription += 'focused, dramatic, '; } // Trim trailing comma and space baseDescription = baseDescription.trim().replace(/,\s*$/, ''); // If no significant characteristics were found, use a generic description if (!baseDescription) { baseDescription = 'balanced, natural'; } // Generate name suggestions based on the description const descriptors = baseDescription.split(', '); // Create a pool of African-inspired prefixes and suffixes const africanPrefixes = ['Mufaro', 'Zim', 'Afro', 'Sahara', 'Serengeti', 'Zambezi', 'Kalahari', 'Savanna']; const africanSuffixes = ['Glow', 'Vibes', 'Tone', 'Mood', 'Look', 'Style', 'Vision', 'Essence']; // Generate suggestions // First suggestion: Combine two random descriptors if (descriptors.length >= 2) { const descriptor1 = descriptors[Math.floor(Math.random() * descriptors.length)]; let descriptor2; do { descriptor2 = descriptors[Math.floor(Math.random() * descriptors.length)]; } while (descriptor2 === descriptor1); const capitalizedDescriptor1 = descriptor1.charAt(0).toUpperCase() + descriptor1.slice(1); const capitalizedDescriptor2 = descriptor2.charAt(0).toUpperCase() + descriptor2.slice(1); suggestions.push(`${capitalizedDescriptor1} ${capitalizedDescriptor2}`); } else if (descriptors.length === 1) { const capitalizedDescriptor = descriptors[0].charAt(0).toUpperCase() + descriptors[0].slice(1); suggestions.push(`${capitalizedDescriptor} Look`); } // Second suggestion: African prefix + descriptor const randomPrefix = africanPrefixes[Math.floor(Math.random() * africanPrefixes.length)]; const randomDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedDescriptor = randomDescriptor.charAt(0).toUpperCase() + randomDescriptor.slice(1); suggestions.push(`${randomPrefix} ${capitalizedDescriptor}`); // Third suggestion: Descriptor + African suffix const randomSuffix = africanSuffixes[Math.floor(Math.random() * africanSuffixes.length)]; const anotherRandomDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedAnotherDescriptor = anotherRandomDescriptor.charAt(0).toUpperCase() + anotherRandomDescriptor.slice(1); suggestions.push(`${capitalizedAnotherDescriptor} ${randomSuffix}`); // If editing an existing preset, include a variation of the existing name if (existingName && !existingName.startsWith('My Custom Style')) { // Split the existing name into words const words = existingName.split(' '); if (words.length > 1) { // Replace one word with a descriptor const wordToReplace = Math.floor(Math.random() * words.length); const newDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedNewDescriptor = newDescriptor.charAt(0).toUpperCase() + newDescriptor.slice(1); words[wordToReplace] = capitalizedNewDescriptor; suggestions[0] = words.join(' '); // Replace the first suggestion } else { // Add a descriptor to the existing name const newDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedNewDescriptor = newDescriptor.charAt(0).toUpperCase() + newDescriptor.slice(1); suggestions[0] = `${existingName} ${capitalizedNewDescriptor}`; } } return suggestions; }; // Generate descriptions based on style names const generateStyleDescription = (styleName) => { const lowercaseName = styleName.toLowerCase(); // Check for common terms in the name if (lowercaseName.includes('vibrant') || lowercaseName.includes('colorful')) { return 'Enhances colors for a lively, energetic look'; } else if (lowercaseName.includes('vintage') || lowercaseName.includes('retro') || lowercaseName.includes('nostalgic')) { return 'Creates a timeless, nostalgic atmosphere'; } else if (lowercaseName.includes('dramatic') || lowercaseName.includes('cinematic')) { return 'Bold, high-contrast look for impactful visuals'; } else if (lowercaseName.includes('warm') || lowercaseName.includes('glow')) { return 'Adds warm tones for a cozy, inviting feel'; } else if (lowercaseName.includes('cool') || lowercaseName.includes('fresh')) { return 'Creates a cool, refreshing atmosphere'; } else if (lowercaseName.includes('soft') || lowercaseName.includes('gentle')) { return 'Subtle adjustments for a soft, delicate look'; } else if (lowercaseName.includes('dark') || lowercaseName.includes('moody')) { return 'Creates a moody, atmospheric ambiance'; } else if (lowercaseName.includes('bright') || lowercaseName.includes('airy')) { return 'Brightens the scene for a light, airy feel'; } else if (lowercaseName.includes('monochrome') || lowercaseName.includes('black')) { return 'Classic black and white with subtle toning'; } else if (lowercaseName.includes('dreamy') || lowercaseName.includes('ethereal')) { return 'Soft focus effect for a dreamy, ethereal quality'; } else if (lowercaseName.includes('afro') || lowercaseName.includes('zim') || lowercaseName.includes('mufaro')) { return 'African-inspired style with rich visual character'; } // Default description return 'Custom style with unique visual characteristics'; }; // Mock AI Vision API for analyzing video content // In a real implementation, this would call a cloud-based AI vision API const analyzeVideoContent = async (videoUri) => { console.log('Analyzing video content:', videoUri); // Simulate API call delay return new Promise((resolve) => { setTimeout(() => { // Mock analysis results // In a real implementation, this would be the response from the AI vision API const mockResults = { dominantColors: [ { color: '#E74C3C', score: 0.35 }, // Red { color: '#3498DB', score: 0.25 }, // Blue { color: '#2ECC71', score: 0.15 }, // Green { color: '#F1C40F', score: 0.15 }, // Yellow { color: '#9B59B6', score: 0.10 }, // Purple ], objects: [ { name: 'person', confidence: 0.95 }, { name: 'tree', confidence: 0.85 }, { name: 'sky', confidence: 0.90 }, { name: 'building', confidence: 0.75 }, ], scenes: [ { name: 'outdoor', confidence: 0.85 }, { name: 'nature', confidence: 0.75 }, { name: 'urban', confidence: 0.65 }, ], lighting: { brightness: 0.7, // 0-1 scale contrast: 0.6, // 0-1 scale colorfulness: 0.8, // 0-1 scale }, time: { daylight: 0.8, // Confidence that it's daytime sunset: 0.6, // Confidence that it's sunset/sunrise night: 0.1, // Confidence that it's nighttime }, mood: { vibrant: 0.7, calm: 0.3, dramatic: 0.5, romantic: 0.4, } }; resolve(mockResults); }, 1500); // Simulate 1.5 second API call }); }; // Function to generate AI style suggestions based on video analysis const generateAIStyleSuggestions = (analysisResults, allPresets) => { // Extract key features from analysis results const { dominantColors, objects, scenes, lighting, time, mood } = analysisResults; // Create a scoring system for each preset based on how well it matches the video content const scoredPresets = allPresets.map(preset => { let score = 0; const maxScore = 100; // Score based on tags matching detected objects and scenes if (preset.tags) { // Check for object matches objects.forEach(obj => { if (preset.tags.includes(obj.name.toLowerCase())) { score += 10 * obj.confidence; } }); // Check for scene matches scenes.forEach(scene => { if (preset.tags.includes(scene.name.toLowerCase())) { score += 15 * scene.confidence; } }); // Check for time of day matches if (time.sunset > 0.5 && preset.tags.includes('sunset')) { score += 15 * time.sunset; } if (time.night > 0.5 && preset.tags.includes('night')) { score += 15 * time.night; } // Check for mood matches if (mood.vibrant > 0.5 && preset.tags.includes('colorful')) { score += 10 * mood.vibrant; } if (mood.dramatic > 0.5 && preset.tags.includes('dramatic')) { score += 10 * mood.dramatic; } if (mood.romantic > 0.5 && preset.tags.includes('romantic')) { score += 10 * mood.romantic; } } // Score based on properties matching lighting conditions if (lighting.brightness < 0.4 && preset.properties.brightness > 1.1) { score += 10; // Brighter preset for dark videos } else if (lighting.brightness > 0.7 && preset.properties.brightness < 0.9) { score += 10; // Darker preset for bright videos } if (lighting.contrast < 0.4 && preset.properties.contrast > 1.2) { score += 10; // Higher contrast for flat videos } if (lighting.colorfulness < 0.4 && preset.properties.saturation > 1.2) { score += 10; // More saturation for dull videos } else if (lighting.colorfulness > 0.7 && preset.properties.saturation < 0.9) { score += 10; // Less saturation for already colorful videos } // Normalize score to 0-100 range score = Math.min(Math.round(score), maxScore); return { ...preset, aiScore: score, matchPercentage: score }; }); // Sort presets by score (highest first) scoredPresets.sort((a, b) => b.aiScore - a.aiScore); // Generate a new custom style based on the analysis const customAiStyle = generateCustomAiStyle(analysisResults); // Return top matches and the custom style return { topMatches: scoredPresets.slice(0, 5), // Top 5 matching presets customStyle: customAiStyle }; }; // Function to generate a custom AI style based on video analysis const generateCustomAiStyle = (analysisResults) => { const { dominantColors, lighting, mood, time } = analysisResults; // Base properties let properties = { brightness: 1, contrast: 1, saturation: 1, blur: 0, sharpen: 0, sepia: 0, grayscale: 0, hueRotate: 0, vignette: 0, }; // Adjust brightness based on lighting if (lighting.brightness < 0.4) { properties.brightness = 1.2; // Brighten dark videos } else if (lighting.brightness > 0.7) { properties.brightness = 0.9; // Slightly dim bright videos } // Adjust contrast based on lighting if (lighting.contrast < 0.4) { properties.contrast = 1.3; // Increase contrast for flat videos } else if (lighting.contrast > 0.7) { properties.contrast = 0.9; // Reduce contrast for high-contrast videos } // Adjust saturation based on colorfulness if (lighting.colorfulness < 0.4) { properties.saturation = 1.3; // Increase saturation for dull videos } else if (lighting.colorfulness > 0.7) { properties.saturation = 0.9; // Reduce saturation for very colorful videos } // Adjust sepia based on time of day if (time.sunset > 0.6) { properties.sepia = 0.3; // Add sepia for sunset scenes properties.hueRotate = 10; // Warm up the colors slightly } // Adjust vignette based on mood if (mood.dramatic > 0.6) { properties.vignette = 0.3; // Add vignette for dramatic scenes properties.contrast = 1.2; // Increase contrast for dramatic effect } // Adjust blur for romantic scenes if (mood.romantic > 0.6) { properties.blur = 1; // Slight blur for romantic scenes properties.saturation = 0.9; // Slightly reduce saturation } // Generate a name for the custom style const styleName = generateAiStyleName(analysisResults); // Create the custom style object const customStyle = { id: `ai_custom_${Date.now()}`, name: styleName, description: `AI-generated style based on your video content`, icon: , properties: properties, cssFilter: generateCssFilter(properties), thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: false, isAiGenerated: true }; return customStyle; }; // Function to generate a name for the AI-generated style const generateAiStyleName = (analysisResults) => { const { scenes, mood, time } = analysisResults; // Get the top scene const topScene = scenes.sort((a, b) => b.confidence - a.confidence)[0]; // Get the top mood const moodEntries = Object.entries(mood); const topMood = moodEntries.sort((a, b) => b[1] - a[1])[0]; // Determine time of day let timeOfDay = 'Day'; if (time.sunset > 0.6) { timeOfDay = 'Sunset'; } else if (time.night > 0.6) { timeOfDay = 'Night'; } // Capitalize first letter const capitalizeFirstLetter = (string) => { return string.charAt(0).toUpperCase() + string.slice(1); }; // Generate name components const sceneComponent = capitalizeFirstLetter(topScene.name); const moodComponent = capitalizeFirstLetter(topMood[0]); // Combine components into a name const styleName = `AI ${sceneComponent} ${moodComponent}`; return styleName; }; // Main component const StylePresetsModal = ({ visible, onClose, videoUri, onApplyStyle, initialPresetId = 'original', userId = 'default_user' // Added userId prop for user-specific rankings }) => { const [selectedPresetId, setSelectedPresetId] = useState(initialPresetId); const [previewUri, setPreviewUri] = useState(videoUri); const [isProcessing, setIsProcessing] = useState(false); const [showAdvancedControls, setShowAdvancedControls] = useState(false); const [customProperties, setCustomProperties] = useState({...STYLE_PRESETS[0].properties}); const [recentPresets, setRecentPresets] = useState([]); const [customPresets, setCustomPresets] = useState([]); const [showCustomEditor, setShowCustomEditor] = useState(false); const [editingPreset, setEditingPreset] = useState(null); const [customStyleName, setCustomStyleName] = useState(''); const [customStyleDescription, setCustomStyleDescription] = useState(''); const [allPresets, setAllPresets] = useState([...STYLE_PRESETS]); const [nameSuggestions, setNameSuggestions] = useState([]); const [isGeneratingSuggestions, setIsGeneratingSuggestions] = useState(false); const [showNameSuggestions, setShowNameSuggestions] = useState(false); // AI suggestion states const [isAnalyzingVideo, setIsAnalyzingVideo] = useState(false); const [videoAnalysisResults, setVideoAnalysisResults] = useState(null); const [aiStyleSuggestions, setAiStyleSuggestions] = useState(null); const [showAiSuggestions, setShowAiSuggestions] = useState(false); // User ranking states const [styleRankings, setStyleRankings] = useState({}); const [showRankings, setShowRankings] = useState(false); // Debounce timer for property changes const propertyChangeTimer = useRef(null); // Constants for ranking system const RANKING_POINTS = { USE: 10, CREATE: 20, EDIT: 15, APPLY: 25 }; const HALF_LIFE_DAYS = 14; // Score halves every 14 days const MAX_AGE_DAYS = 90; // Ignore interactions older than 90 days // Load saved presets and rankings from AsyncStorage useEffect(() => { const loadSavedData = async () => { try { // Load selected preset const savedPresetId = await AsyncStorage.getItem('selectedStylePreset'); if (savedPresetId) { setSelectedPresetId(savedPresetId); } // Load recent presets const savedRecentPresets = await AsyncStorage.getItem('recentStylePresets'); if (savedRecentPresets) { setRecentPresets(JSON.parse(savedRecentPresets)); } // Load custom presets const savedCustomPresets = await AsyncStorage.getItem('customStylePresets'); if (savedCustomPresets) { const parsedCustomPresets = JSON.parse(savedCustomPresets); setCustomPresets(parsedCustomPresets); // Merge custom presets with default presets setAllPresets([...parsedCustomPresets, ...STYLE_PRESETS]); } // Load style rankings for this user const savedRankings = await AsyncStorage.getItem(`styleRankings_${userId}`); if (savedRankings) { setStyleRankings(JSON.parse(savedRankings)); } } catch (error) { console.error('Error loading saved data:', error); } }; loadSavedData(); }, [userId]); // Analyze video content when modal opens useEffect(() => { if (visible && videoUri && !videoAnalysisResults) { handleAnalyzeVideo(); } }, [visible, videoUri]); // Generate name suggestions when custom properties change useEffect(() => { if (showCustomEditor) { // Clear previous timer if (propertyChangeTimer.current) { clearTimeout(propertyChangeTimer.current); } // Set a new timer to debounce property changes propertyChangeTimer.current = setTimeout(() => { setIsGeneratingSuggestions(true); // Generate suggestions based on properties const existingName = editingPreset ? editingPreset.name : null; const suggestions = generateStyleNameSuggestions(customProperties, existingName); setNameSuggestions(suggestions); // If no custom name has been set, use the first suggestion if (customStyleName === 'My Custom Style' || !customStyleName) { setCustomStyleName(suggestions[0]); setCustomStyleDescription(generateStyleDescription(suggestions[0])); } setIsGeneratingSuggestions(false); }, 500); // 500ms debounce return () => { if (propertyChangeTimer.current) { clearTimeout(propertyChangeTimer.current); } }; } }, [customProperties, showCustomEditor, editingPreset]); // Calculate current scores for all presets based on ranking history const calculateCurrentScores = () => { const now = new Date(); const scores = {}; // Process each preset's history Object.entries(styleRankings).forEach(([presetId, history]) => { let totalScore = 0; history.forEach(entry => { const entryDate = new Date(entry.timestamp); const ageInDays = (now - entryDate) / (1000 * 60 * 60 * 24); // Skip entries older than MAX_AGE_DAYS if (ageInDays > MAX_AGE_DAYS) return; // Apply time decay to the score const decayFactor = Math.pow(0.5, ageInDays / HALF_LIFE_DAYS); const decayedScore = entry.points * decayFactor; totalScore += decayedScore; }); scores[presetId] = Math.round(totalScore); }); return scores; }; // Sort presets by their ranking scores const sortPresetsByRanking = (presets) => { const scores = calculateCurrentScores(); return [...presets].sort((a, b) => { const scoreA = scores[a.id] || 0; const scoreB = scores[b.id] || 0; return scoreB - scoreA; }); }; // Update style ranking when a preset is used const updateStyleRanking = async (presetId, action) => { try { // Determine points based on action let points = RANKING_POINTS.USE; // Default switch (action) { case 'create': points = RANKING_POINTS.CREATE; break; case 'edit': points = RANKING_POINTS.EDIT; break; case 'apply': points = RANKING_POINTS.APPLY; break; } // Create new ranking entry const newEntry = { timestamp: new Date().toISOString(), points: points }; // Update rankings state const updatedRankings = { ...styleRankings }; if (!updatedRankings[presetId]) { updatedRankings[presetId] = []; } updatedRankings[presetId].push(newEntry); setStyleRankings(updatedRankings); // Save to AsyncStorage await AsyncStorage.setItem(`styleRankings_${userId}`, JSON.stringify(updatedRankings)); } catch (error) { console.error('Error updating style ranking:', error); } }; // Save selected preset to AsyncStorage const saveSelectedPreset = async (presetId) => { try { await AsyncStorage.setItem('selectedStylePreset', presetId); // Update recent presets const updatedRecentPresets = [presetId, ...recentPresets.filter(id => id !== presetId)].slice(0, 3); setRecentPresets(updatedRecentPresets); await AsyncStorage.setItem('recentStylePresets', JSON.stringify(updatedRecentPresets)); // Update ranking for this preset updateStyleRanking(presetId, 'use'); } catch (error) { console.error('Error saving style preset:', error); } }; // Save custom presets to AsyncStorage const saveCustomPresets = async (presets) => { try { await AsyncStorage.setItem('customStylePresets', JSON.stringify(presets)); } catch (error) { console.error('Error saving custom presets:', error); } }; // Apply style preset to video const applyStylePreset = useCallback((presetId, videoObject) => { setIsProcessing(true); // Find the selected preset const selectedPreset = allPresets.find(preset => preset.id === presetId) || STYLE_PRESETS[0]; // In a real implementation, this would apply actual video processing // For this prototype, we'll simulate processing with a timeout setTimeout(() => { // Create a new video object with the applied style const processedVideo = { ...videoObject, stylePreset: selectedPreset.id, cssFilter: selectedPreset.cssFilter || generateCssFilter(selectedPreset.properties), properties: {...selectedPreset.properties} }; setPreviewUri(videoObject.uri); setIsProcessing(false); // Return the processed video return processedVideo; }, 500); }, [allPresets]); // Handle preset selection const handleSelectPreset = (presetId) => { setSelectedPresetId(presetId); saveSelectedPreset(presetId); // Find the selected preset const selectedPreset = allPresets.find(preset => preset.id === presetId); if (selectedPreset) { setCustomProperties({...selectedPreset.properties}); } // Apply the preset to the video preview const videoObject = { uri: videoUri }; applyStylePreset(presetId, videoObject); }; // Handle apply button press const handleApply = () => { const selectedPreset = allPresets.find(preset => preset.id === selectedPresetId) || STYLE_PRESETS[0]; // Update ranking for applying this preset updateStyleRanking(selectedPresetId, 'apply'); // Call the parent component's onApplyStyle function with the selected preset if (onApplyStyle) { onApplyStyle(selectedPresetId, selectedPreset.properties); } onClose(); }; // Handle edit preset const handleEditPreset = (preset) => { setEditingPreset(preset); setCustomProperties({...preset.properties}); setCustomStyleName(preset.name); setCustomStyleDescription(preset.description); setShowCustomEditor(true); // Generate initial name suggestions const suggestions = generateStyleNameSuggestions(preset.properties, preset.name); setNameSuggestions(suggestions); // Update ranking for editing this preset updateStyleRanking(preset.id, 'edit'); }; // Handle create new custom style const handleCreateCustomStyle = () => { // Start with original properties const originalPreset = STYLE_PRESETS[0]; setEditingPreset(null); setCustomProperties({...originalPreset.properties}); setCustomStyleName('My Custom Style'); setCustomStyleDescription('Custom filter created by me'); setShowCustomEditor(true); // Generate initial name suggestions const suggestions = generateStyleNameSuggestions(originalPreset.properties); setNameSuggestions(suggestions); }; // Handle save custom style const handleSaveCustomStyle = () => { if (!customStyleName.trim()) { // If no name is provided, use the first suggestion if (nameSuggestions.length > 0) { setCustomStyleName(nameSuggestions[0]); setCustomStyleDescription(generateStyleDescription(nameSuggestions[0])); } else { Alert.alert('Error', 'Please enter a name for your custom style'); return; } } // Generate CSS filter const cssFilter = generateCssFilter(customProperties); if (editingPreset && !editingPreset.isDefault) { // Update existing custom preset const updatedCustomPresets = customPresets.map(preset => preset.id === editingPreset.id ? { ...preset, name: customStyleName, description: customStyleDescription, properties: {...customProperties}, cssFilter } : preset ); setCustomPresets(updatedCustomPresets); saveCustomPresets(updatedCustomPresets); // Update allPresets setAllPresets([...updatedCustomPresets, ...STYLE_PRESETS]); // Select the updated preset setSelectedPresetId(editingPreset.id); saveSelectedPreset(editingPreset.id); // Update ranking for editing this preset updateStyleRanking(editingPreset.id, 'edit'); } else { // Create new custom preset const newPresetId = `custom_${Date.now()}`; const newPreset = { id: newPresetId, name: customStyleName, description: customStyleDescription || generateStyleDescription(customStyleName), icon: , properties: {...customProperties}, cssFilter, thumbnail: videoUri || 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: false }; const updatedCustomPresets = [newPreset, ...customPresets]; setCustomPresets(updatedCustomPresets); saveCustomPresets(updatedCustomPresets); // Update allPresets setAllPresets([...updatedCustomPresets, ...STYLE_PRESETS]); // Select the new preset setSelectedPresetId(newPresetId); saveSelectedPreset(newPresetId); // Update ranking for creating this preset updateStyleRanking(newPresetId, 'create'); } // Close the custom editor setShowCustomEditor(false); }; // Handle delete custom style const handleDeleteCustomStyle = (presetId) => { Alert.alert( 'Delete Custom Style', 'Are you sure you want to delete this custom style?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Delete', style: 'destructive', onPress: async () => { // Remove from custom presets const updatedCustomPresets = customPresets.filter(preset => preset.id !== presetId); setCustomPresets(updatedCustomPresets); saveCustomPresets(updatedCustomPresets); // Update allPresets setAllPresets([...updatedCustomPresets, ...STYLE_PRESETS]); // If the deleted preset was selected, select the original preset if (selectedPresetId === presetId) { setSelectedPresetId('original'); saveSelectedPreset('original'); } // If in edit mode and deleting the current preset, exit edit mode if (editingPreset && editingPreset.id === presetId) { setShowCustomEditor(false); } // Remove ranking data for this preset try { const updatedRankings = { ...styleRankings }; delete updatedRankings[presetId]; setStyleRankings(updatedRankings); await AsyncStorage.setItem(`styleRankings_${userId}`, JSON.stringify(updatedRankings)); } catch (error) { console.error('Error removing ranking data:', error); } } } ] ); }; // Handle property change in custom editor const handlePropertyChange = (property, value) => { setCustomProperties(prev => ({ ...prev, [property]: value })); }; // Handle name suggestion selection const handleSelectNameSuggestion = (suggestion) => { setCustomStyleName(suggestion); setCustomStyleDescription(generateStyleDescription(suggestion)); setShowNameSuggestions(false); }; // Handle analyze video button press const handleAnalyzeVideo = async () => { setIsAnalyzingVideo(true); setShowAiSuggestions(true); try { // Call the AI vision API to analyze the video const analysisResults = await analyzeVideoContent(videoUri); setVideoAnalysisResults(analysisResults); // Generate style suggestions based on the analysis const suggestions = generateAIStyleSuggestions(analysisResults, allPresets); setAiStyleSuggestions(suggestions); } catch (error) { console.error('Error analyzing video:', error); Alert.alert('Analysis Error', 'Failed to analyze video content'); } finally { setIsAnalyzingVideo(false); } }; // Handle apply AI style const handleApplyAiStyle = (preset) => { // If it's a new custom style, add it to custom presets if (preset.isAiGenerated && !customPresets.some(p => p.id === preset.id)) { const updatedCustomPresets = [preset, ...customPresets]; setCustomPresets(updatedCustomPresets); saveCustomPresets(updatedCustomPresets); // Update allPresets setAllPresets([...updatedCustomPresets, ...STYLE_PRESETS]); // Update ranking for creating this preset updateStyleRanking(preset.id, 'create'); } // Select and apply the preset setSelectedPresetId(preset.id); saveSelectedPreset(preset.id); // Apply the preset to the video preview const videoObject = { uri: videoUri }; applyStylePreset(preset.id, videoObject); // Close the AI suggestions panel setShowAiSuggestions(false); }; // Update preview in real-time when customProperties change useEffect(() => { if (showCustomEditor) { // Apply custom properties to preview const cssFilter = generateCssFilter(customProperties); // Skipped direct DOM manipulation for React Native: //const videoPreviewElement = document.getElementById('video-preview'); //if (videoPreviewElement) { // videoPreviewElement.style.filter = cssFilter; //} // Instead, update is handled via style in component render below } }, [customProperties, showCustomEditor]); // Render preset item const renderPresetItem = ({ item }) => { const isSelected = selectedPresetId === item.id; const scores = calculateCurrentScores(); const score = scores[item.id] || 0; const isFrequentlyUsed = score > 50; return ( handleSelectPreset(item.id)} > {isSelected && ( )} {showRankings && isFrequentlyUsed && ( )} {item.name} {showRankings ? ( Score: {score} ) : ( {item.description} )} {/* Edit button */} handleEditPreset(item)} > {/* Delete button (only for custom presets) */} {!item.isDefault && ( handleDeleteCustomStyle(item.id)} > )} ); }; // Render AI suggestion item const renderAiSuggestionItem = ({ item }) => { return ( handleApplyAiStyle(item)} > {item.matchPercentage}% match {item.name} {item.description} ); }; // Render custom presets section const renderCustomPresetsSection = () => { if (customPresets.length === 0) return null; // Sort custom presets by ranking if showRankings is true const presetsToShow = showRankings ? sortPresetsByRanking(customPresets) : customPresets; return ( My Custom Styles setShowRankings(!showRankings)} > item.id} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.presetsList} /> ); }; // Render AI suggestions section const renderAiSuggestionsSection = () => { if (!showAiSuggestions) return null; return ( AI Style Suggestions setShowAiSuggestions(false)} > {isAnalyzingVideo ? ( Analyzing video content... Finding the perfect style for your video ) : aiStyleSuggestions ? ( {/* Custom AI Style */} AI-Generated Custom Style handleApplyAiStyle(aiStyleSuggestions.customStyle)} > {aiStyleSuggestions.customStyle.name} {aiStyleSuggestions.customStyle.description} handleApplyAiStyle(aiStyleSuggestions.customStyle)} > Apply Custom Style {/* Matching Presets */} Matching Presets item.id} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.aiSuggestionsList} /> ) : ( Failed to generate suggestions Retry )} ); }; // Render name suggestion item const renderNameSuggestionItem = ({ item }) => ( handleSelectNameSuggestion(item)} > {item} ); // Render custom style editor const renderCustomStyleEditor = () => { return ( setShowCustomEditor(false)} > {editingPreset ? 'Edit Style' : 'Create Custom Style'} {/* Style Preview */} {/* Style Name and Description */} { setCustomStyleName(text); setShowNameSuggestions(text.length > 0); }} placeholder="Style Name" placeholderTextColor="#999999" onFocus={() => setShowNameSuggestions(true)} /> {/* AI Suggestions Button */} setShowNameSuggestions(!showNameSuggestions)} > {/* Name Suggestions */} {showNameSuggestions && ( {isGeneratingSuggestions ? ( ) : ( `suggestion-${index}`} horizontal={false} showsVerticalScrollIndicator={false} /> )} )} {/* Property Sliders */} Brightness {customProperties.brightness.toFixed(2)} handlePropertyChange('brightness', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Contrast {customProperties.contrast.toFixed(2)} handlePropertyChange('contrast', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Saturation {customProperties.saturation.toFixed(2)} handlePropertyChange('saturation', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Blur {customProperties.blur.toFixed(1)}px handlePropertyChange('blur', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Sepia {customProperties.sepia.toFixed(2)} handlePropertyChange('sepia', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Grayscale {customProperties.grayscale.toFixed(2)} handlePropertyChange('grayscale', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Hue Rotate {customProperties.hueRotate.toFixed(0)}° handlePropertyChange('hueRotate', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Vignette {customProperties.vignette.toFixed(2)} handlePropertyChange('vignette', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> {/* Reset Button */} { if (editingPreset) { setCustomProperties({...editingPreset.properties}); } else { setCustomProperties({...STYLE_PRESETS[0].properties}); } }} > Reset Properties ); }; // Get the selected preset const selectedPreset = allPresets.find(preset => preset.id === selectedPresetId) || STYLE_PRESETS[0]; // Get recent presets const recentPresetItems = recentPresets .map(id => allPresets.find(preset => preset.id === id)) .filter(Boolean); // Get default presets sorted by ranking if showRankings is true const defaultPresets = showRankings ? sortPresetsByRanking(STYLE_PRESETS) : STYLE_PRESETS; return ( {showCustomEditor ? ( renderCustomStyleEditor() ) : ( {/* Header */} Style Presets {/* Video Preview */} {isProcessing && ( Applying style... )} {selectedPreset.name} {/* Action Buttons */} {/* Create Custom Style Button */} Create Custom {/* AI Suggestions Button */} AI Suggestions {/* AI Suggestions Section */} {renderAiSuggestionsSection()} {/* Recent Presets */} {recentPresetItems.length > 0 && ( Recent {recentPresetItems.map((preset) => ( handleSelectPreset(preset.id)} > {preset.name} ))} )} {/* Custom Presets */} {renderCustomPresetsSection()} {/* All Presets */} All Styles setShowRankings(!showRankings)} > item.id} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.presetsList} /> {/* Action Buttons */} handleSelectPreset('original')}> Reset Apply Style )} ); }; // Example usage of the component const StylePresetsExample = () => { const [modalVisible, setModalVisible] = useState(false); const [currentVideo, setCurrentVideo] = useState({ uri: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', stylePreset: 'original' }); const handleApplyStyle = (presetId, properties) => { // Update the video with the selected style setCurrentVideo({ ...currentVideo, stylePreset: presetId, properties: properties }); console.log(`Applied style preset: ${presetId}`); }; return ( setModalVisible(true)} > Open Style Presets setModalVisible(false)} videoUri={currentVideo.uri} onApplyStyle={handleApplyStyle} initialPresetId={currentVideo.stylePreset} userId="user123" // Example user ID /> ); }; const styles = StyleSheet.create({ // Modal Styles modalContainer: { flex: 1, backgroundColor: 'rgba(0,0,0,0.8)', justifyContent: 'flex-end', }, modalContent: { backgroundColor: '#1A1A1A', borderTopLeftRadius: 20, borderTopRightRadius: 20, paddingBottom: 30, // Extra padding for bottom safe area height: height * 0.85, // Take up 85% of screen height }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 16, borderBottomWidth: 1, borderBottomColor: '#333333', }, modalTitle: { fontSize: 18, fontWeight: '700', color: '#FFFFFF', fontFamily: 'Ubuntu, sans-serif', }, closeButton: { padding: 8, }, applyButton: { padding: 8, }, // Preview Styles previewContainer: { padding: 16, alignItems: 'center', }, videoPreview: { width: width * 0.6, height: width * 0.6 * 1.78, // 16:9 aspect ratio borderRadius: 12, overflow: 'hidden', backgroundColor: '#000000', marginBottom: 12, }, previewImage: { width: '100%', height: '100%', }, previewTitle: { fontSize: 16, fontWeight: '600', color: '#FFFFFF', fontFamily: 'Inter, sans-serif', }, processingOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center', }, processingText: { color: '#FFFFFF', marginTop: 12, fontSize: 14, fontFamily: 'Inter, sans-serif', }, // Action Buttons Row actionButtonsRow: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 16, marginBottom: 16, }, actionButtonSecondary: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#333333', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 8, flex: 1, marginHorizontal: 4, }, actionButtonSecondaryText: { color: '#FFFFFF', fontSize: 14, fontWeight: '600', marginLeft: 8, fontFamily: 'Inter, sans-serif', }, // AI Suggestions Styles aiSuggestionsContainer: { backgroundColor: '#2A2A2A', borderRadius: 12, marginHorizontal: 16, marginBottom: 16, overflow: 'hidden', }, aiSuggestionsHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, backgroundColor: '#4D2C91', }, aiSuggestionsTitle: { fontSize: 16, fontWeight: '700', color: '#FFFFFF', fontFamily: 'Ubuntu, sans-serif', }, aiSuggestionsCloseButton: { padding: 4, }, aiSuggestionsLoadingContainer: { padding: 24, alignItems: 'center', }, aiSuggestionsLoadingText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600', marginTop: 16, fontFamily: 'Inter, sans-serif', }, aiSuggestionsLoadingSubtext: { color: '#CCCCCC', fontSize: 14, marginTop: 8, fontFamily: 'Inter, sans-serif', }, aiSuggestionsContent: { padding: 16, }, aiCustomStyleContainer: { marginBottom: 16, }, aiCustomStyleTitle: { fontSize: 14, fontWeight: '600', color: '#FFFFFF', marginBottom: 8, fontFamily: 'Ubuntu, sans-serif', }, aiCustomStyleCard: { backgroundColor: '#333333', borderRadius: 8, overflow: 'hidden', }, aiCustomStyleImageContainer: { height: 120, backgroundColor: '#000000', }, aiCustomStyleImage: { width: '100%', height: '100%', }, aiCustomStyleInfo: { padding: 12, }, aiCustomStyleName: { fontSize: 16, fontWeight: '600', color: '#FFFFFF', marginBottom: 4, fontFamily: 'Inter, sans-serif', }, aiCustomStyleDescription: { fontSize: 12, color: '#CCCCCC', marginBottom: 12, fontFamily: 'Inter, sans-serif', }, aiCustomStyleApplyButton: { backgroundColor: '#4D2C91', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 4, alignSelf: 'flex-start', }, aiCustomStyleApplyButtonText: { color: '#FFFFFF', fontSize: 12, fontWeight: '600', fontFamily: 'Inter, sans-serif', }, aiMatchingPresetsTitle: { fontSize: 14, fontWeight: '600', color: '#FFFFFF', marginBottom: 8, fontFamily: 'Ubuntu, sans-serif', }, aiSuggestionsList: { paddingBottom: 8, }, aiSuggestionItem: { width: 120, marginRight: 12, }, aiSuggestionThumbnailContainer: { width: 120, height: 120, borderRadius: 8, overflow: 'hidden', marginBottom: 8, position: 'relative', }, aiSuggestionThumbnail: { width: '100%', height: '100%', }, aiSuggestionMatchContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'rgba(0,0,0,0.7)', paddingVertical: 4, paddingHorizontal: 8, }, aiSuggestionMatchText: { color: '#FFFFFF', fontSize: 12, fontWeight: '600', textAlign: 'center', fontFamily: 'Inter, sans-serif', }, aiSuggestionName: { fontSize: 14, fontWeight: '600', color: '#FFFFFF', marginBottom: 4, fontFamily: 'Inter, sans-serif', }, aiSuggestionDescription: { fontSize: 12, color: '#CCCCCC', fontFamily: 'Inter, sans-serif', }, aiSuggestionsErrorContainer: { padding: 24, alignItems: 'center', }, aiSuggestionsErrorText: { color: '#FF6B6B', fontSize: 16, fontWeight: '600', marginBottom: 16, fontFamily: 'Inter, sans-serif', }, aiSuggestionsRetryButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#4D2C91', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 4, }, aiSuggestionsRetryButtonText: { color: '#FFFFFF', fontSize: 14, fontWeight: '600', marginLeft: 8, fontFamily: 'Inter, sans-serif', }, // Recent Presets Styles recentPresetsContainer: { paddingHorizontal: 16, marginBottom: 16, }, sectionHeaderRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, }, sectionTitle: { fontSize: 16, fontWeight: '600', color: '#FFFFFF', fontFamily: 'Ubuntu, sans-serif', }, rankingToggleButton: { padding: 4, }, recentPresetItem: { backgroundColor: '#333333', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, marginRight: 8, }, recentPresetItemSelected: { backgroundColor: '#4D2C91', }, recentPresetName: { color: '#FFFFFF', fontSize: 14, fontFamily: 'Inter, sans-serif', }, // Custom Presets Styles customPresetsContainer: { paddingHorizontal: 16, marginBottom: 16, }, // All Presets Styles allPresetsContainer: { paddingHorizontal: 16, marginBottom: 16, }, presetsList: { paddingBottom: 8, }, presetItemContainer: { marginRight: 12, position: 'relative', }, presetItem: { width: 120, }, presetItemSelected: { opacity: 1, }, presetItemFrequent: { borderWidth: 2, borderColor: '#FFD700', borderRadius: 8, padding: 4, }, presetThumbnailContainer: { width: 120, height: 120, borderRadius: 8, overflow: 'hidden', marginBottom: 8, position: 'relative', }, presetThumbnail: { width: '100%', height: '100%', }, presetSelectedIndicator: { position: 'absolute', top: 8, right: 8, width: 24, height: 24, borderRadius: 12, backgroundColor: '#4D2C91', justifyContent: 'center', alignItems: 'center', }, presetRankingIndicator: { position: 'absolute', top: 8, left: 8, width: 24, height: 24, borderRadius: 12, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center', }, presetName: { fontSize: 14, fontWeight: '600', color: '#FFFFFF', marginBottom: 4, fontFamily: 'Inter, sans-serif', }, presetDescription: { fontSize: 12, color: '#CCCCCC', fontFamily: 'Inter, sans-serif', }, presetRankingScore: { fontSize: 12, color: '#FFD700', fontFamily: 'Inter, sans-serif', }, editButton: { position: 'absolute', top: 8, left: 8, width: 24, height: 24, borderRadius: 12, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center', }, deleteButton: { position: 'absolute', top: 40, left: 8, width: 24, height: 24, borderRadius: 12, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center', }, // Action Buttons Styles actionButtonsContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 16, marginTop: 16, }, actionButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#333333', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 8, flex: 1, marginHorizontal: 4, }, actionButtonPrimary: { backgroundColor: '#4D2C91', }, actionButtonText: { color: '#FFFFFF', marginLeft: 8, fontSize: 14, fontWeight: '600', fontFamily: 'Inter, sans-serif', }, // Custom Style Editor Styles customEditorContainer: { flex: 1, backgroundColor: '#1A1A1A', }, customEditorHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 16, borderBottomWidth: 1, borderBottomColor: '#333333', }, customEditorTitle: { fontSize: 18, fontWeight: '700', color: '#FFFFFF', fontFamily: 'Ubuntu, sans-serif', }, backButton: { padding: 8, }, saveButton: { padding: 8, }, styleInfoContainer: { paddingHorizontal: 16, paddingVertical: 12, }, styleNameContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, }, styleNameInput: { flex: 1, backgroundColor: '#333333', color: '#FFFFFF', paddingHorizontal: 12, paddingVertical: 10, borderRadius: 8, fontSize: 16, fontFamily: 'Inter, sans-serif', }, suggestionsButton: { backgroundColor: '#333333', padding: 10, borderRadius: 8, marginLeft: 8, }, styleDescriptionInput: { backgroundColor: '#333333', color: '#FFFFFF', paddingHorizontal: 12, paddingVertical: 10, borderRadius: 8, fontSize: 14, fontFamily: 'Inter, sans-serif', }, // Name Suggestions Styles nameSuggestionsContainer: { backgroundColor: '#333333', borderRadius: 8, padding: 8, marginBottom: 8, maxHeight: 120, }, nameSuggestionItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8, paddingHorizontal: 12, borderBottomWidth: 1, borderBottomColor: '#444444', }, nameSuggestionText: { color: '#FFFFFF', fontSize: 14, marginLeft: 8, fontFamily: 'Inter, sans-serif', }, // Property Sliders Styles propertySliders: { flex: 1, paddingHorizontal: 16, }, sliderContainer: { marginBottom: 16, }, sliderLabelContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, sliderLabel: { color: '#FFFFFF', fontSize: 14, fontFamily: 'Inter, sans-serif', }, sliderValue: { color: '#CCCCCC', fontSize: 14, fontFamily: 'Inter, sans-serif', }, slider: { width: '100%', height: 40, }, resetPropertiesButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#333333', paddingVertical: 12, borderRadius: 8, marginVertical: 16, }, resetPropertiesButtonText: { color: '#FFFFFF', marginLeft: 8, fontSize: 14, fontWeight: '600', fontFamily: 'Inter, sans-serif', }, // Example Component Styles exampleContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F9F5FF', }, exampleButton: { backgroundColor: '#4D2C91', paddingVertical: 12, paddingHorizontal: 24, borderRadius: 8, }, exampleButtonText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600', fontFamily: 'Inter, sans-serif', }, }); export default Style import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, Text, Modal, TouchableOpacity, FlatList, Image, StyleSheet, Dimensions, Animated, ScrollView, ActivityIndicator, TextInput, Alert } from 'react-native'; import Slider from '@react-native-community/slider'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { X, Check, Eye, Sliders, RefreshCw, Save, Film, Zap, Sun, Contrast, Droplet, Aperture, Layers, Edit2, Trash2, ChevronLeft, Plus, Sparkles, Lightbulb } from 'lucide-react'; const { width, height } = Dimensions.get('window'); // Define style presets with their properties const STYLE_PRESETS = [ { id: 'original', name: 'Original', description: 'No filters applied', icon: , properties: { brightness: 1, contrast: 1, saturation: 1, blur: 0, sharpen: 0, sepia: 0, grayscale: 0, hueRotate: 0, vignette: 0, }, cssFilter: '', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true }, { id: 'cinematic', name: 'Cinematic', description: 'Professional film look with rich colors', icon: , properties: { brightness: 0.9, contrast: 1.2, saturation: 1.1, blur: 0, sharpen: 0.2, sepia: 0.1, grayscale: 0, hueRotate: 0, vignette: 0.2, }, cssFilter: 'brightness(0.9) contrast(1.2) saturate(1.1) sepia(0.1)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true }, { id: 'vibrant', name: 'Vibrant', description: 'Bright and colorful enhancement', icon: , properties: { brightness: 1.1, contrast: 1.3, saturation: 1.5, blur: 0, sharpen: 0.1, sepia: 0, grayscale: 0, hueRotate: 0, vignette: 0, }, cssFilter: 'brightness(1.1) contrast(1.3) saturate(1.5)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true }, { id: 'blackAndWhite', name: 'Black & White', description: 'Classic monochrome look', icon: , properties: { brightness: 1, contrast: 1.2, saturation: 0, blur: 0, sharpen: 0.1, sepia: 0, grayscale: 1, hueRotate: 0, vignette: 0.1, }, cssFilter: 'grayscale(1) contrast(1.2)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true }, { id: 'vintage', name: 'Vintage', description: 'Nostalgic retro film effect', icon: , properties: { brightness: 0.9, contrast: 0.9, saturation: 0.8, blur: 0, sharpen: 0, sepia: 0.5, grayscale: 0.1, hueRotate: 10, vignette: 0.3, }, cssFilter: 'brightness(0.9) contrast(0.9) saturate(0.8) sepia(0.5) grayscale(0.1) hue-rotate(10deg)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true }, { id: 'warm', name: 'Warm', description: 'Cozy warm tones', icon: , properties: { brightness: 1.05, contrast: 1.1, saturation: 1.2, blur: 0, sharpen: 0, sepia: 0.3, grayscale: 0, hueRotate: 5, vignette: 0.1, }, cssFilter: 'brightness(1.05) contrast(1.1) saturate(1.2) sepia(0.3) hue-rotate(5deg)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true }, { id: 'cool', name: 'Cool', description: 'Fresh cool tones', icon: , properties: { brightness: 1, contrast: 1.1, saturation: 0.9, blur: 0, sharpen: 0, sepia: 0, grayscale: 0.1, hueRotate: -10, vignette: 0.1, }, cssFilter: 'brightness(1) contrast(1.1) saturate(0.9) grayscale(0.1) hue-rotate(-10deg)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true }, { id: 'dramatic', name: 'Dramatic', description: 'High contrast cinematic look', icon: , properties: { brightness: 0.85, contrast: 1.5, saturation: 0.9, blur: 0, sharpen: 0.3, sepia: 0.1, grayscale: 0.1, hueRotate: 0, vignette: 0.4, }, cssFilter: 'brightness(0.85) contrast(1.5) saturate(0.9) sepia(0.1) grayscale(0.1)', thumbnail: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: true } ]; // Function to generate CSS filter string from properties const generateCssFilter = (properties) => { return ` brightness(${properties.brightness}) contrast(${properties.contrast}) saturate(${properties.saturation}) blur(${properties.blur}px) sepia(${properties.sepia}) grayscale(${properties.grayscale}) hue-rotate(${properties.hueRotate}deg) `; }; // AI-powered name suggestion system const generateStyleNameSuggestions = (properties, existingName = null) => { // This is a simplified AI model that analyzes properties and suggests names // In a real implementation, this would call an API or use a more sophisticated model const suggestions = []; let baseDescription = ''; // Analyze brightness if (properties.brightness < 0.8) { baseDescription += 'dark, moody, '; } else if (properties.brightness > 1.2) { baseDescription += 'bright, airy, '; } // Analyze contrast if (properties.contrast > 1.3) { baseDescription += 'high-contrast, punchy, '; } else if (properties.contrast < 0.9) { baseDescription += 'soft, gentle, '; } // Analyze saturation if (properties.saturation > 1.3) { baseDescription += 'vibrant, colorful, '; } else if (properties.saturation < 0.7) { baseDescription += 'muted, subtle, '; } // Analyze sepia if (properties.sepia > 0.3) { baseDescription += 'vintage, nostalgic, '; } // Analyze grayscale if (properties.grayscale > 0.5) { baseDescription += 'monochrome, classic, '; } // Analyze hue rotation if (Math.abs(properties.hueRotate) > 30) { baseDescription += 'surreal, artistic, '; } // Analyze blur if (properties.blur > 2) { baseDescription += 'dreamy, ethereal, '; } // Analyze vignette if (properties.vignette > 0.3) { baseDescription += 'focused, dramatic, '; } // Trim trailing comma and space baseDescription = baseDescription.trim().replace(/,\s*$/, ''); // If no significant characteristics were found, use a generic description if (!baseDescription) { baseDescription = 'balanced, natural'; } // Generate name suggestions based on the description const descriptors = baseDescription.split(', '); // Create a pool of African-inspired prefixes and suffixes const africanPrefixes = ['Mufaro', 'Zim', 'Afro', 'Sahara', 'Serengeti', 'Zambezi', 'Kalahari', 'Savanna']; const africanSuffixes = ['Glow', 'Vibes', 'Tone', 'Mood', 'Look', 'Style', 'Vision', 'Essence']; // Generate suggestions // First suggestion: Combine two random descriptors if (descriptors.length >= 2) { const descriptor1 = descriptors[Math.floor(Math.random() * descriptors.length)]; let descriptor2; do { descriptor2 = descriptors[Math.floor(Math.random() * descriptors.length)]; } while (descriptor2 === descriptor1); const capitalizedDescriptor1 = descriptor1.charAt(0).toUpperCase() + descriptor1.slice(1); const capitalizedDescriptor2 = descriptor2.charAt(0).toUpperCase() + descriptor2.slice(1); suggestions.push(`${capitalizedDescriptor1} ${capitalizedDescriptor2}`); } else if (descriptors.length === 1) { const capitalizedDescriptor = descriptors[0].charAt(0).toUpperCase() + descriptors[0].slice(1); suggestions.push(`${capitalizedDescriptor} Look`); } // Second suggestion: African prefix + descriptor const randomPrefix = africanPrefixes[Math.floor(Math.random() * africanPrefixes.length)]; const randomDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedDescriptor = randomDescriptor.charAt(0).toUpperCase() + randomDescriptor.slice(1); suggestions.push(`${randomPrefix} ${capitalizedDescriptor}`); // Third suggestion: Descriptor + African suffix const randomSuffix = africanSuffixes[Math.floor(Math.random() * africanSuffixes.length)]; const anotherRandomDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedAnotherDescriptor = anotherRandomDescriptor.charAt(0).toUpperCase() + anotherRandomDescriptor.slice(1); suggestions.push(`${capitalizedAnotherDescriptor} ${randomSuffix}`); // If editing an existing preset, include a variation of the existing name if (existingName && !existingName.startsWith('My Custom Style')) { // Split the existing name into words const words = existingName.split(' '); if (words.length > 1) { // Replace one word with a descriptor const wordToReplace = Math.floor(Math.random() * words.length); const newDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedNewDescriptor = newDescriptor.charAt(0).toUpperCase() + newDescriptor.slice(1); words[wordToReplace] = capitalizedNewDescriptor; suggestions[0] = words.join(' '); // Replace the first suggestion } else { // Add a descriptor to the existing name const newDescriptor = descriptors[Math.floor(Math.random() * descriptors.length)]; const capitalizedNewDescriptor = newDescriptor.charAt(0).toUpperCase() + newDescriptor.slice(1); suggestions[0] = `${existingName} ${capitalizedNewDescriptor}`; } } return suggestions; }; // Generate descriptions based on style names const generateStyleDescription = (styleName) => { const lowercaseName = styleName.toLowerCase(); // Check for common terms in the name if (lowercaseName.includes('vibrant') || lowercaseName.includes('colorful')) { return 'Enhances colors for a lively, energetic look'; } else if (lowercaseName.includes('vintage') || lowercaseName.includes('retro') || lowercaseName.includes('nostalgic')) { return 'Creates a timeless, nostalgic atmosphere'; } else if (lowercaseName.includes('dramatic') || lowercaseName.includes('cinematic')) { return 'Bold, high-contrast look for impactful visuals'; } else if (lowercaseName.includes('warm') || lowercaseName.includes('glow')) { return 'Adds warm tones for a cozy, inviting feel'; } else if (lowercaseName.includes('cool') || lowercaseName.includes('fresh')) { return 'Creates a cool, refreshing atmosphere'; } else if (lowercaseName.includes('soft') || lowercaseName.includes('gentle')) { return 'Subtle adjustments for a soft, delicate look'; } else if (lowercaseName.includes('dark') || lowercaseName.includes('moody')) { return 'Creates a moody, atmospheric ambiance'; } else if (lowercaseName.includes('bright') || lowercaseName.includes('airy')) { return 'Brightens the scene for a light, airy feel'; } else if (lowercaseName.includes('monochrome') || lowercaseName.includes('black')) { return 'Classic black and white with subtle toning'; } else if (lowercaseName.includes('dreamy') || lowercaseName.includes('ethereal')) { return 'Soft focus effect for a dreamy, ethereal quality'; } else if (lowercaseName.includes('afro') || lowercaseName.includes('zim') || lowercaseName.includes('mufaro')) { return 'African-inspired style with rich visual character'; } // Default description return 'Custom style with unique visual characteristics'; }; // Main component const StylePresetsModal = ({ visible, onClose, videoUri, onApplyStyle, initialPresetId = 'original' }) => { const [selectedPresetId, setSelectedPresetId] = useState(initialPresetId); const [previewUri, setPreviewUri] = useState(videoUri); const [isProcessing, setIsProcessing] = useState(false); const [showAdvancedControls, setShowAdvancedControls] = useState(false); const [customProperties, setCustomProperties] = useState({...STYLE_PRESETS[0].properties}); const [recentPresets, setRecentPresets] = useState([]); const [customPresets, setCustomPresets] = useState([]); const [showCustomEditor, setShowCustomEditor] = useState(false); const [editingPreset, setEditingPreset] = useState(null); const [customStyleName, setCustomStyleName] = useState(''); const [customStyleDescription, setCustomStyleDescription] = useState(''); const [allPresets, setAllPresets] = useState([...STYLE_PRESETS]); const [nameSuggestions, setNameSuggestions] = useState([]); const [isGeneratingSuggestions, setIsGeneratingSuggestions] = useState(false); const [showNameSuggestions, setShowNameSuggestions] = useState(false); // Debounce timer for property changes const propertyChangeTimer = useRef(null); // Load saved presets from AsyncStorage useEffect(() => { const loadSavedPresets = async () => { try { // Load selected preset const savedPresetId = await AsyncStorage.getItem('selectedStylePreset'); if (savedPresetId) { setSelectedPresetId(savedPresetId); } // Load recent presets const savedRecentPresets = await AsyncStorage.getItem('recentStylePresets'); if (savedRecentPresets) { setRecentPresets(JSON.parse(savedRecentPresets)); } // Load custom presets const savedCustomPresets = await AsyncStorage.getItem('customStylePresets'); if (savedCustomPresets) { const parsedCustomPresets = JSON.parse(savedCustomPresets); setCustomPresets(parsedCustomPresets); // Merge custom presets with default presets setAllPresets([...parsedCustomPresets, ...STYLE_PRESETS]); } } catch (error) { console.error('Error loading saved style presets:', error); } }; loadSavedPresets(); }, []); // Generate name suggestions when custom properties change useEffect(() => { if (showCustomEditor) { // Clear previous timer if (propertyChangeTimer.current) { clearTimeout(propertyChangeTimer.current); } // Set a new timer to debounce property changes propertyChangeTimer.current = setTimeout(() => { setIsGeneratingSuggestions(true); // Generate suggestions based on properties const existingName = editingPreset ? editingPreset.name : null; const suggestions = generateStyleNameSuggestions(customProperties, existingName); setNameSuggestions(suggestions); // If no custom name has been set, use the first suggestion if (customStyleName === 'My Custom Style' || !customStyleName) { setCustomStyleName(suggestions[0]); setCustomStyleDescription(generateStyleDescription(suggestions[0])); } setIsGeneratingSuggestions(false); }, 500); // 500ms debounce return () => { if (propertyChangeTimer.current) { clearTimeout(propertyChangeTimer.current); } }; } }, [customProperties, showCustomEditor, editingPreset]); // Save selected preset to AsyncStorage const saveSelectedPreset = async (presetId) => { try { await AsyncStorage.setItem('selectedStylePreset', presetId); // Update recent presets const updatedRecentPresets = [presetId, ...recentPresets.filter(id => id !== presetId)].slice(0, 3); setRecentPresets(updatedRecentPresets); await AsyncStorage.setItem('recentStylePresets', JSON.stringify(updatedRecentPresets)); } catch (error) { console.error('Error saving style preset:', error); } }; // Save custom presets to AsyncStorage const saveCustomPresets = async (presets) => { try { await AsyncStorage.setItem('customStylePresets', JSON.stringify(presets)); } catch (error) { console.error('Error saving custom presets:', error); } }; // Apply style preset to video const applyStylePreset = useCallback((presetId, videoObject) => { setIsProcessing(true); // Find the selected preset const selectedPreset = allPresets.find(preset => preset.id === presetId) || STYLE_PRESETS[0]; // In a real implementation, this would apply actual video processing // For this prototype, we'll simulate processing with a timeout setTimeout(() => { // Create a new video object with the applied style const processedVideo = { ...videoObject, stylePreset: selectedPreset.id, cssFilter: selectedPreset.cssFilter || generateCssFilter(selectedPreset.properties), properties: {...selectedPreset.properties} }; setPreviewUri(videoObject.uri); setIsProcessing(false); // Return the processed video return processedVideo; }, 500); }, [allPresets]); // Handle preset selection const handleSelectPreset = (presetId) => { setSelectedPresetId(presetId); saveSelectedPreset(presetId); // Find the selected preset const selectedPreset = allPresets.find(preset => preset.id === presetId); if (selectedPreset) { setCustomProperties({...selectedPreset.properties}); } // Apply the preset to the video preview const videoObject = { uri: videoUri }; applyStylePreset(presetId, videoObject); }; // Handle apply button press const handleApply = () => { const selectedPreset = allPresets.find(preset => preset.id === selectedPresetId) || STYLE_PRESETS[0]; // Call the parent component's onApplyStyle function with the selected preset if (onApplyStyle) { onApplyStyle(selectedPresetId, selectedPreset.properties); } onClose(); }; // Handle edit preset const handleEditPreset = (preset) => { setEditingPreset(preset); setCustomProperties({...preset.properties}); setCustomStyleName(preset.name); setCustomStyleDescription(preset.description); setShowCustomEditor(true); // Generate initial name suggestions const suggestions = generateStyleNameSuggestions(preset.properties, preset.name); setNameSuggestions(suggestions); }; // Handle create new custom style const handleCreateCustomStyle = () => { // Start with original properties const originalPreset = STYLE_PRESETS[0]; setEditingPreset(null); setCustomProperties({...originalPreset.properties}); setCustomStyleName('My Custom Style'); setCustomStyleDescription('Custom filter created by me'); setShowCustomEditor(true); // Generate initial name suggestions const suggestions = generateStyleNameSuggestions(originalPreset.properties); setNameSuggestions(suggestions); }; // Handle save custom style const handleSaveCustomStyle = () => { if (!customStyleName.trim()) { // If no name is provided, use the first suggestion if (nameSuggestions.length > 0) { setCustomStyleName(nameSuggestions[0]); setCustomStyleDescription(generateStyleDescription(nameSuggestions[0])); } else { Alert.alert('Error', 'Please enter a name for your custom style'); return; } } // Generate CSS filter const cssFilter = generateCssFilter(customProperties); if (editingPreset && !editingPreset.isDefault) { // Update existing custom preset const updatedCustomPresets = customPresets.map(preset => preset.id === editingPreset.id ? { ...preset, name: customStyleName, description: customStyleDescription, properties: {...customProperties}, cssFilter } : preset ); setCustomPresets(updatedCustomPresets); saveCustomPresets(updatedCustomPresets); // Update allPresets setAllPresets([...updatedCustomPresets, ...STYLE_PRESETS]); // Select the updated preset setSelectedPresetId(editingPreset.id); saveSelectedPreset(editingPreset.id); } else { // Create new custom preset const newPresetId = `custom_${Date.now()}`; const newPreset = { id: newPresetId, name: customStyleName, description: customStyleDescription || generateStyleDescription(customStyleName), icon: , properties: {...customProperties}, cssFilter, thumbnail: videoUri || 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', isDefault: false }; const updatedCustomPresets = [newPreset, ...customPresets]; setCustomPresets(updatedCustomPresets); saveCustomPresets(updatedCustomPresets); // Update allPresets setAllPresets([...updatedCustomPresets, ...STYLE_PRESETS]); // Select the new preset setSelectedPresetId(newPresetId); saveSelectedPreset(newPresetId); } // Close the custom editor setShowCustomEditor(false); }; // Handle delete custom style const handleDeleteCustomStyle = (presetId) => { Alert.alert( 'Delete Custom Style', 'Are you sure you want to delete this custom style?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Delete', style: 'destructive', onPress: () => { // Remove from custom presets const updatedCustomPresets = customPresets.filter(preset => preset.id !== presetId); setCustomPresets(updatedCustomPresets); saveCustomPresets(updatedCustomPresets); // Update allPresets setAllPresets([...updatedCustomPresets, ...STYLE_PRESETS]); // If the deleted preset was selected, select the original preset if (selectedPresetId === presetId) { setSelectedPresetId('original'); saveSelectedPreset('original'); } // If in edit mode and deleting the current preset, exit edit mode if (editingPreset && editingPreset.id === presetId) { setShowCustomEditor(false); } } } ] ); }; // Handle property change in custom editor const handlePropertyChange = (property, value) => { setCustomProperties(prev => ({ ...prev, [property]: value })); }; // Handle name suggestion selection const handleSelectNameSuggestion = (suggestion) => { setCustomStyleName(suggestion); setCustomStyleDescription(generateStyleDescription(suggestion)); setShowNameSuggestions(false); }; // Update preview in real-time when customProperties change useEffect(() => { if (showCustomEditor) { // Apply custom properties to preview const cssFilter = generateCssFilter(customProperties); // Skipped direct DOM manipulation for React Native: //const videoPreviewElement = document.getElementById('video-preview'); //if (videoPreviewElement) { // videoPreviewElement.style.filter = cssFilter; //} // Instead, update is handled via style in component render below } }, [customProperties, showCustomEditor]); // Render preset item const renderPresetItem = ({ item }) => { const isSelected = selectedPresetId === item.id; return ( handleSelectPreset(item.id)} > {isSelected && ( )} {item.name} {item.description} {/* Edit button */} handleEditPreset(item)} > {/* Delete button (only for custom presets) */} {!item.isDefault && ( handleDeleteCustomStyle(item.id)} > )} ); }; // Render custom presets section const renderCustomPresetsSection = () => { if (customPresets.length === 0) return null; return ( My Custom Styles item.id} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.presetsList} /> ); }; // Render name suggestion item const renderNameSuggestionItem = ({ item }) => ( handleSelectNameSuggestion(item)} > {item} ); // Render custom style editor const renderCustomStyleEditor = () => { return ( setShowCustomEditor(false)} > {editingPreset ? 'Edit Style' : 'Create Custom Style'} {/* Style Preview */} {/* Style Name and Description */} { setCustomStyleName(text); setShowNameSuggestions(text.length > 0); }} placeholder="Style Name" placeholderTextColor="#999999" onFocus={() => setShowNameSuggestions(true)} /> {/* AI Suggestions Button */} setShowNameSuggestions(!showNameSuggestions)} > {/* Name Suggestions */} {showNameSuggestions && ( {isGeneratingSuggestions ? ( ) : ( `suggestion-${index}`} horizontal={false} showsVerticalScrollIndicator={false} /> )} )} {/* Property Sliders */} Brightness {customProperties.brightness.toFixed(2)} handlePropertyChange('brightness', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Contrast {customProperties.contrast.toFixed(2)} handlePropertyChange('contrast', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Saturation {customProperties.saturation.toFixed(2)} handlePropertyChange('saturation', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Blur {customProperties.blur.toFixed(1)}px handlePropertyChange('blur', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Sepia {customProperties.sepia.toFixed(2)} handlePropertyChange('sepia', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Grayscale {customProperties.grayscale.toFixed(2)} handlePropertyChange('grayscale', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Hue Rotate {customProperties.hueRotate.toFixed(0)}° handlePropertyChange('hueRotate', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> Vignette {customProperties.vignette.toFixed(2)} handlePropertyChange('vignette', value)} minimumTrackTintColor="#4D2C91" maximumTrackTintColor="#333333" thumbTintColor="#FF6B00" /> {/* Reset Button */} { if (editingPreset) { setCustomProperties({...editingPreset.properties}); } else { setCustomProperties({...STYLE_PRESETS[0].properties}); } }} > Reset Properties ); }; // Get the selected preset const selectedPreset = allPresets.find(preset => preset.id === selectedPresetId) || STYLE_PRESETS[0]; // Get recent presets const recentPresetItems = recentPresets .map(id => allPresets.find(preset => preset.id === id)) .filter(Boolean); return ( {showCustomEditor ? ( renderCustomStyleEditor() ) : ( {/* Header */} Style Presets {/* Video Preview */} {isProcessing && ( Applying style... )} {selectedPreset.name} {/* Create Custom Style Button */} Create Custom Style {/* Recent Presets */} {recentPresetItems.length > 0 && ( Recent {recentPresetItems.map((preset) => ( handleSelectPreset(preset.id)} > {preset.name} ))} )} {customPresets.length === 0 ? null : ( My Custom Styles item.id} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.presetsList} /> )} All Styles item.id} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.presetsList} /> {/* Action Buttons */} handleSelectPreset('original')}> Reset Apply Style )} ); }; // Example usage of the component const StylePresetsExample = () => { const [modalVisible, setModalVisible] = useState(false); const [currentVideo, setCurrentVideo] = useState({ uri: 'https://assets.sitebrew.ai/A_smartphone_displaying_the_Mu-1746729837007.webp', stylePreset: 'original' }); const handleApplyStyle = (presetId, properties) => { // Update the video with the selected style setCurrentVideo({ ...currentVideo, stylePreset: presetId, properties: properties }); console.log(`Applied style preset: ${presetId}`); }; return ( setModalVisible(true)} > Open Style Presets setModalVisible(false)} videoUri={currentVideo.uri} onApplyStyle={handleApplyStyle} initialPresetId={currentVideo.stylePreset} /> ); }; const styles = StyleSheet.create({ // Modal Styles modalContainer: { flex: 1, backgroundColor: 'rgba(0,0,0,0.8)', justifyContent: 'flex-end', }, modalContent: { backgroundColor: '#1A1A1A', borderTopLeftRadius: 20, borderTopRightRadius: 20, paddingBottom: 30, // Extra padding for bottom safe area height: height * 0.85, // Take up 85% of screen height }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 16, borderBottomWidth: 1, borderBottomColor: '#333333', }, modalTitle: { fontSize: 18, fontWeight: '700', color: '#FFFFFF', fontFamily: 'Ubuntu, sans-serif', }, closeButton: { padding: 8, }, applyButton: { padding: 8, }, // Preview Styles previewContainer: { padding: 16, alignItems: 'center', }, videoPreview: { width: width * 0.6, height: width * 0.6 * 1.78, // 16:9 aspect ratio borderRadius: 12, overflow: 'hidden', backgroundColor: '#000000', marginBottom: 12, }, previewImage: { width: '100%', height: '100%', }, previewTitle: { fontSize: 16, fontWeight: '600', color: '#FFFFFF', fontFamily: 'Inter, sans-serif', }, processingOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center', }, processingText: { color: '#FFFFFF', marginTop: 12, fontSize: 14, fontFamily: 'Inter, sans-serif', }, // Create Custom Style Button createCustomStyleButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#4D2C91', marginHorizontal: 16, paddingVertical: 12, borderRadius: 8, marginBottom: 16, }, createCustomStyleText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600', marginLeft: 8, fontFamily: 'Inter, sans-serif', }, // Recent Presets Styles recentPresetsContainer: { paddingHorizontal: 16, marginBottom: 16, }, sectionTitle: { fontSize: 16, fontWeight: '600', color: '#FFFFFF', marginBottom: 12, fontFamily: 'Ubuntu, sans-serif', }, recentPresetItem: { backgroundColor: '#333333', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, marginRight: 8, }, recentPresetItemSelected: { backgroundColor: '#4D2C91', }, recentPresetName: { color: '#FFFFFF', fontSize: 14, fontFamily: 'Inter, sans-serif', }, // Custom Presets Styles customPresetsContainer: { paddingHorizontal: 16, marginBottom: 16, }, // All Presets Styles allPresetsContainer: { paddingHorizontal: 16, marginBottom: 16, }, presetsList: { paddingBottom: 8, }, presetItemContainer: { marginRight: 12, position: 'relative', }, presetItem: { width: 120, }, presetItemSelected: { opacity: 1, }, presetThumbnailContainer: { width: 120, height: 120, borderRadius: 8, overflow: 'hidden', marginBottom: 8, position: 'relative', }, presetThumbnail: { width: '100%', height: '100%', }, presetSelectedIndicator: { position: 'absolute', top: 8, right: 8, width: 24, height: 24, borderRadius: 12, backgroundColor: '#4D2C91', justifyContent: 'center', alignItems: 'center', }, presetName: { fontSize: 14, fontWeight: '600', color: '#FFFFFF', marginBottom: 4, fontFamily: 'Inter, sans-serif', }, presetDescription: { fontSize: 12, color: '#CCCCCC', fontFamily: 'Inter, sans-serif', }, editButton: { position: 'absolute', top: 8, left: 8, width: 24, height: 24, borderRadius: 12, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center', }, deleteButton: { position: 'absolute', top: 40, left: 8, width: 24, height: 24, borderRadius: 12, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center', }, // Action Buttons Styles actionButtonsContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 16, marginTop: 16, }, actionButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#333333', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 8, flex: 1, marginHorizontal: 4, }, actionButtonPrimary: { backgroundColor: '#4D2C91', }, actionButtonText: { color: '#FFFFFF', marginLeft: 8, fontSize: 14, fontWeight: '600', fontFamily: 'Inter, sans-serif', }, // Custom Style Editor Styles customEditorContainer: { flex: 1, backgroundColor: '#1A1A1A', }, customEditorHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 16, borderBottomWidth: 1, borderBottomColor: '#333333', }, customEditorTitle: { fontSize: 18, fontWeight: '700', color: '#FFFFFF', fontFamily: 'Ubuntu, sans-serif', }, backButton: { padding: 8, }, saveButton: { padding: 8, }, styleInfoContainer: { paddingHorizontal: 16, paddingVertical: 12, }, styleNameContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, }, styleNameInput: { flex: 1, backgroundColor: '#333333', color: '#FFFFFF', paddingHorizontal: 12, paddingVertical: 10, borderRadius: 8, fontSize: 16, fontFamily: 'Inter, sans-serif', }, suggestionsButton: { backgroundColor: '#333333', padding: 10, borderRadius: 8, marginLeft: 8, }, styleDescriptionInput: { backgroundColor: '#333333', color: '#FFFFFF', paddingHorizontal: 12, paddingVertical: 10, borderRadius: 8, fontSize: 14, fontFamily: 'Inter, sans-serif', }, // Name Suggestions Styles nameSuggestionsContainer: { backgroundColor: '#333333', borderRadius: 8, padding: 8, marginBottom: 8, maxHeight: 120, }, nameSuggestionItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8, paddingHorizontal: 12, borderBottomWidth: 1, borderBottomColor: '#444444', }, nameSuggestionText: { color: '#FFFFFF', fontSize: 14, marginLeft: 8, fontFamily: 'Inter, sans-serif', }, // Property Sliders Styles propertySliders: { flex: 1, paddingHorizontal: 16, }, sliderContainer: { marginBottom: 16, }, sliderLabelContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, sliderLabel: { color: '#FFFFFF', fontSize: 14, fontFamily: 'Inter, sans-serif', }, sliderValue: { color: '#CCCCCC', fontSize: 14, fontFamily: 'Inter, sans-serif', }, slider: { width: '100%', height: 40, }, resetPropertiesButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#333333', paddingVertical: 12, borderRadius: 8, marginVertical: 16, }, resetPropertiesButtonText: { color: '#FFFFFF', marginLeft: 8, fontSize: 14, fontWeight: '600', fontFamily: 'Inter, sans-serif', }, // Example Component Styles exampleContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F9F5FF', }, exampleButton: { backgroundColor: '#4D2C91', paddingVertical: 12, paddingHorizontal: 24, borderRadius: 8, }, exampleButtonText: { color: '#FFFFFF', fontSize: 16, fontWeight: '600', fontFamily: 'Inter, sans-serif', }, }); export default StylePresetsModal;