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;