mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
Parental Overlay Localization Patch
This commit is contained in:
parent
6bfade4a17
commit
db9d12491d
3 changed files with 343 additions and 279 deletions
|
|
@ -1,40 +1,41 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { View, Text, StyleSheet, Dimensions } from 'react-native';
|
import { View, Text, StyleSheet, Dimensions } from 'react-native';
|
||||||
import Animated, {
|
import Animated, {
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
withTiming,
|
withTiming,
|
||||||
withDelay,
|
withDelay,
|
||||||
Easing,
|
Easing,
|
||||||
SharedValue,
|
SharedValue,
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { parentalGuideService } from '../../../services/parentalGuideService';
|
import { parentalGuideService } from '../../../services/parentalGuideService';
|
||||||
import { logger } from '../../../utils/logger';
|
import { logger } from '../../../utils/logger';
|
||||||
import { useTheme } from '../../../contexts/ThemeContext';
|
import { useTheme } from '../../../contexts/ThemeContext';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface ParentalGuideOverlayProps {
|
interface ParentalGuideOverlayProps {
|
||||||
imdbId: string | undefined;
|
imdbId: string | undefined;
|
||||||
type: 'movie' | 'series';
|
type: 'movie' | 'series';
|
||||||
season?: number;
|
season?: number;
|
||||||
episode?: number;
|
episode?: number;
|
||||||
shouldShow: boolean;
|
shouldShow: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WarningItem {
|
interface WarningItem {
|
||||||
label: string;
|
label: string;
|
||||||
severity: string;
|
severity: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatLabel = (key: string): string => {
|
const formatLabel = (key: string): string => {
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
nudity: 'Nudity',
|
nudity: 'Nudity',
|
||||||
violence: 'Violence',
|
violence: 'Violence',
|
||||||
profanity: 'Profanity',
|
profanity: 'Profanity',
|
||||||
alcohol: 'Alcohol/Drugs',
|
alcohol: 'Alcohol/Drugs',
|
||||||
frightening: 'Frightening',
|
frightening: 'Frightening',
|
||||||
};
|
};
|
||||||
return labels[key] || key;
|
return labels[key] || key;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Row height for calculating line animation
|
// Row height for calculating line animation
|
||||||
|
|
@ -42,303 +43,344 @@ const ROW_HEIGHT = 18;
|
||||||
|
|
||||||
// Separate component for each warning item
|
// Separate component for each warning item
|
||||||
const WarningItemView: React.FC<{
|
const WarningItemView: React.FC<{
|
||||||
item: WarningItem;
|
item: WarningItem;
|
||||||
opacity: SharedValue<number>;
|
opacity: SharedValue<number>;
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
}> = ({ item, opacity, fontSize }) => {
|
}> = ({ item, opacity, fontSize }) => {
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
opacity: opacity.value,
|
opacity: opacity.value,
|
||||||
}));
|
}));
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.warningItem, animatedStyle]}>
|
<Animated.View style={[styles.warningItem, animatedStyle]}>
|
||||||
<Text style={[styles.label, { fontSize }]}>{item.label}</Text>
|
<Text style={[styles.label, { fontSize }]}>
|
||||||
<Text style={[styles.separator, { fontSize }]}>·</Text>
|
{/* replace method added to support "Alcohol/Drugs" label */}
|
||||||
<Text style={[styles.severity, { fontSize }]}>{item.severity}</Text>
|
{t(`overlay_screen.${item.label.toLowerCase().replace('/', '_')}`)}
|
||||||
</Animated.View>
|
</Text>
|
||||||
);
|
<Text style={[styles.separator, { fontSize }]}>·</Text>
|
||||||
|
<Text style={[styles.severity, { fontSize }]}>
|
||||||
|
{t(`overlay_screen.${item.severity.toLowerCase()}`)}
|
||||||
|
</Text>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
|
export const ParentalGuideOverlay: React.FC<ParentalGuideOverlayProps> = ({
|
||||||
imdbId,
|
imdbId,
|
||||||
type,
|
type,
|
||||||
season,
|
season,
|
||||||
episode,
|
episode,
|
||||||
shouldShow,
|
shouldShow,
|
||||||
}) => {
|
}) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const screenWidth = Dimensions.get('window').width;
|
const screenWidth = Dimensions.get('window').width;
|
||||||
const [warnings, setWarnings] = useState<WarningItem[]>([]);
|
const [warnings, setWarnings] = useState<WarningItem[]>([]);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const hasShownRef = useRef(false);
|
const hasShownRef = useRef(false);
|
||||||
const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const fadeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const fadeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const prevShouldShowRef = useRef<boolean>(false);
|
const prevShouldShowRef = useRef<boolean>(false);
|
||||||
|
|
||||||
// Animation values
|
// Animation values
|
||||||
const lineHeight = useSharedValue(0);
|
const lineHeight = useSharedValue(0);
|
||||||
const containerOpacity = useSharedValue(0);
|
const containerOpacity = useSharedValue(0);
|
||||||
const itemOpacity0 = useSharedValue(0);
|
const itemOpacity0 = useSharedValue(0);
|
||||||
const itemOpacity1 = useSharedValue(0);
|
const itemOpacity1 = useSharedValue(0);
|
||||||
const itemOpacity2 = useSharedValue(0);
|
const itemOpacity2 = useSharedValue(0);
|
||||||
const itemOpacity3 = useSharedValue(0);
|
const itemOpacity3 = useSharedValue(0);
|
||||||
const itemOpacity4 = useSharedValue(0);
|
const itemOpacity4 = useSharedValue(0);
|
||||||
|
|
||||||
const itemOpacities = [itemOpacity0, itemOpacity1, itemOpacity2, itemOpacity3, itemOpacity4];
|
const itemOpacities = [
|
||||||
|
itemOpacity0,
|
||||||
|
itemOpacity1,
|
||||||
|
itemOpacity2,
|
||||||
|
itemOpacity3,
|
||||||
|
itemOpacity4,
|
||||||
|
];
|
||||||
|
|
||||||
// Fetch parental guide data
|
// Fetch parental guide data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!imdbId) return;
|
if (!imdbId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data;
|
let data;
|
||||||
if (type === 'movie') {
|
if (type === 'movie') {
|
||||||
data = await parentalGuideService.getMovieGuide(imdbId);
|
data = await parentalGuideService.getMovieGuide(imdbId);
|
||||||
} else if (type === 'series' && season && episode) {
|
} else if (type === 'series' && season && episode) {
|
||||||
data = await parentalGuideService.getTVGuide(imdbId, season, episode);
|
data = await parentalGuideService.getTVGuide(imdbId, season, episode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data && data.parentalGuide) {
|
if (data && data.parentalGuide) {
|
||||||
const guide = data.parentalGuide;
|
const guide = data.parentalGuide;
|
||||||
const items: WarningItem[] = [];
|
const items: WarningItem[] = [];
|
||||||
|
|
||||||
Object.entries(guide).forEach(([key, severity]) => {
|
Object.entries(guide).forEach(([key, severity]) => {
|
||||||
if (severity && severity.toLowerCase() !== 'none') {
|
if (severity && severity.toLowerCase() !== 'none') {
|
||||||
items.push({
|
items.push({
|
||||||
label: formatLabel(key),
|
label: formatLabel(key),
|
||||||
severity: severity,
|
severity: severity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const severityOrder = { severe: 0, moderate: 1, mild: 2, none: 3 };
|
const severityOrder = { severe: 0, moderate: 1, mild: 2, none: 3 };
|
||||||
items.sort((a, b) => {
|
items.sort((a, b) => {
|
||||||
const orderA = severityOrder[a.severity.toLowerCase() as keyof typeof severityOrder] ?? 3;
|
const orderA =
|
||||||
const orderB = severityOrder[b.severity.toLowerCase() as keyof typeof severityOrder] ?? 3;
|
severityOrder[a.severity.toLowerCase() as keyof typeof severityOrder] ??
|
||||||
return orderA - orderB;
|
3;
|
||||||
});
|
const orderB =
|
||||||
|
severityOrder[b.severity.toLowerCase() as keyof typeof severityOrder] ??
|
||||||
|
3;
|
||||||
|
return orderA - orderB;
|
||||||
|
});
|
||||||
|
|
||||||
setWarnings(items.slice(0, 5));
|
setWarnings(items.slice(0, 5));
|
||||||
logger.log('[ParentalGuideOverlay] Loaded warnings:', items.length);
|
logger.log('[ParentalGuideOverlay] Loaded warnings:', items.length);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[ParentalGuideOverlay] Error fetching guide:', error);
|
logger.error('[ParentalGuideOverlay] Error fetching guide:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [imdbId, type, season, episode]);
|
}, [imdbId, type, season, episode]);
|
||||||
|
|
||||||
// Handle show/hide based on shouldShow (controls visibility)
|
// Handle show/hide based on shouldShow (controls visibility)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// When controls are shown (shouldShow becomes false), immediately hide overlay
|
// When controls are shown (shouldShow becomes false), immediately hide overlay
|
||||||
if (!shouldShow && isVisible) {
|
if (!shouldShow && isVisible) {
|
||||||
// Clear any pending timeouts
|
// Clear any pending timeouts
|
||||||
if (hideTimeoutRef.current) {
|
if (hideTimeoutRef.current) {
|
||||||
clearTimeout(hideTimeoutRef.current);
|
clearTimeout(hideTimeoutRef.current);
|
||||||
hideTimeoutRef.current = null;
|
hideTimeoutRef.current = null;
|
||||||
}
|
}
|
||||||
if (fadeTimeoutRef.current) {
|
if (fadeTimeoutRef.current) {
|
||||||
clearTimeout(fadeTimeoutRef.current);
|
clearTimeout(fadeTimeoutRef.current);
|
||||||
fadeTimeoutRef.current = null;
|
fadeTimeoutRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Immediately hide overlay with quick fade out
|
// Immediately hide overlay with quick fade out
|
||||||
const count = warnings.length;
|
const count = warnings.length;
|
||||||
// FADE OUT: Items fade out in reverse order (bottom to top)
|
// FADE OUT: Items fade out in reverse order (bottom to top)
|
||||||
for (let i = count - 1; i >= 0; i--) {
|
for (let i = count - 1; i >= 0; i--) {
|
||||||
const reverseDelay = (count - 1 - i) * 40;
|
const reverseDelay = (count - 1 - i) * 40;
|
||||||
itemOpacities[i].value = withDelay(
|
itemOpacities[i].value = withDelay(
|
||||||
reverseDelay,
|
reverseDelay,
|
||||||
withTiming(0, { duration: 100 })
|
withTiming(0, { duration: 100 }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line shrinks after items are gone
|
// Line shrinks after items are gone
|
||||||
const lineDelay = count * 40 + 50;
|
const lineDelay = count * 40 + 50;
|
||||||
lineHeight.value = withDelay(lineDelay, withTiming(0, {
|
lineHeight.value = withDelay(
|
||||||
duration: 200,
|
lineDelay,
|
||||||
easing: Easing.in(Easing.cubic),
|
withTiming(0, {
|
||||||
}));
|
duration: 200,
|
||||||
|
easing: Easing.in(Easing.cubic),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Container fades out last
|
// Container fades out last
|
||||||
containerOpacity.value = withDelay(lineDelay + 100, withTiming(0, { duration: 150 }));
|
containerOpacity.value = withDelay(
|
||||||
|
lineDelay + 100,
|
||||||
|
withTiming(0, { duration: 150 }),
|
||||||
|
);
|
||||||
|
|
||||||
// Set invisible after all animations complete
|
// Set invisible after all animations complete
|
||||||
fadeTimeoutRef.current = setTimeout(() => {
|
fadeTimeoutRef.current = setTimeout(() => {
|
||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
// Don't reset hasShownRef here - only reset on content change
|
// Don't reset hasShownRef here - only reset on content change
|
||||||
}, lineDelay + 300);
|
}, lineDelay + 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When controls are hidden (shouldShow becomes true), show overlay if not already shown for this content
|
|
||||||
// Only show if transitioning from false to true (controls just hidden)
|
|
||||||
if (shouldShow && !prevShouldShowRef.current && warnings.length > 0 && !hasShownRef.current) {
|
|
||||||
hasShownRef.current = true;
|
|
||||||
setIsVisible(true);
|
|
||||||
|
|
||||||
const count = warnings.length;
|
// When controls are hidden (shouldShow becomes true), show overlay if not already shown for this content
|
||||||
// Line height = (row height * count) + (gap * (count - 1))
|
// Only show if transitioning from false to true (controls just hidden)
|
||||||
const gap = 2; // matches styles.itemsContainer gap
|
if (
|
||||||
const totalLineHeight = (count * ROW_HEIGHT) + ((count - 1) * gap);
|
shouldShow &&
|
||||||
|
!prevShouldShowRef.current &&
|
||||||
|
warnings.length > 0 &&
|
||||||
|
!hasShownRef.current
|
||||||
|
) {
|
||||||
|
hasShownRef.current = true;
|
||||||
|
setIsVisible(true);
|
||||||
|
|
||||||
// Container fade in
|
const count = warnings.length;
|
||||||
containerOpacity.value = withTiming(1, { duration: 300 });
|
// Line height = (row height * count) + (gap * (count - 1))
|
||||||
|
const gap = 2; // matches styles.itemsContainer gap
|
||||||
|
const totalLineHeight = count * ROW_HEIGHT + (count - 1) * gap;
|
||||||
|
|
||||||
// FADE IN: Line grows from top to bottom first
|
// Container fade in
|
||||||
lineHeight.value = withTiming(totalLineHeight, {
|
containerOpacity.value = withTiming(1, { duration: 300 });
|
||||||
duration: 400,
|
|
||||||
easing: Easing.out(Easing.cubic),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then each item fades in one by one (after line animation)
|
// FADE IN: Line grows from top to bottom first
|
||||||
for (let i = 0; i < count; i++) {
|
lineHeight.value = withTiming(totalLineHeight, {
|
||||||
itemOpacities[i].value = withDelay(
|
duration: 400,
|
||||||
400 + i * 80, // Start after line, stagger each
|
easing: Easing.out(Easing.cubic),
|
||||||
withTiming(1, { duration: 200 })
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-hide after 5 seconds
|
// Then each item fades in one by one (after line animation)
|
||||||
hideTimeoutRef.current = setTimeout(() => {
|
for (let i = 0; i < count; i++) {
|
||||||
// FADE OUT: Items fade out in reverse order (bottom to top)
|
itemOpacities[i].value = withDelay(
|
||||||
for (let i = count - 1; i >= 0; i--) {
|
400 + i * 80, // Start after line, stagger each
|
||||||
const reverseDelay = (count - 1 - i) * 60;
|
withTiming(1, { duration: 200 }),
|
||||||
itemOpacities[i].value = withDelay(
|
);
|
||||||
reverseDelay,
|
}
|
||||||
withTiming(0, { duration: 150 })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line shrinks after items are gone
|
// Auto-hide after 5 seconds
|
||||||
const lineDelay = count * 60 + 100;
|
hideTimeoutRef.current = setTimeout(() => {
|
||||||
lineHeight.value = withDelay(lineDelay, withTiming(0, {
|
// FADE OUT: Items fade out in reverse order (bottom to top)
|
||||||
duration: 300,
|
for (let i = count - 1; i >= 0; i--) {
|
||||||
easing: Easing.in(Easing.cubic),
|
const reverseDelay = (count - 1 - i) * 60;
|
||||||
}));
|
itemOpacities[i].value = withDelay(
|
||||||
|
reverseDelay,
|
||||||
|
withTiming(0, { duration: 150 }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Container fades out last
|
// Line shrinks after items are gone
|
||||||
containerOpacity.value = withDelay(lineDelay + 200, withTiming(0, { duration: 200 }));
|
const lineDelay = count * 60 + 100;
|
||||||
|
lineHeight.value = withDelay(
|
||||||
|
lineDelay,
|
||||||
|
withTiming(0, {
|
||||||
|
duration: 300,
|
||||||
|
easing: Easing.in(Easing.cubic),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Set invisible after all animations complete
|
// Container fades out last
|
||||||
fadeTimeoutRef.current = setTimeout(() => {
|
containerOpacity.value = withDelay(
|
||||||
setIsVisible(false);
|
lineDelay + 200,
|
||||||
// Don't reset hasShownRef - only reset on content change
|
withTiming(0, { duration: 200 }),
|
||||||
}, lineDelay + 500);
|
);
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update previous shouldShow value
|
// Set invisible after all animations complete
|
||||||
prevShouldShowRef.current = shouldShow;
|
fadeTimeoutRef.current = setTimeout(() => {
|
||||||
}, [shouldShow, isVisible, warnings.length]);
|
setIsVisible(false);
|
||||||
|
// Don't reset hasShownRef - only reset on content change
|
||||||
|
}, lineDelay + 500);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Update previous shouldShow value
|
||||||
useEffect(() => {
|
prevShouldShowRef.current = shouldShow;
|
||||||
return () => {
|
}, [shouldShow, isVisible, warnings.length]);
|
||||||
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
|
|
||||||
if (fadeTimeoutRef.current) clearTimeout(fadeTimeoutRef.current);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Reset when content changes
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
hasShownRef.current = false;
|
return () => {
|
||||||
prevShouldShowRef.current = false;
|
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
|
||||||
setWarnings([]);
|
if (fadeTimeoutRef.current) clearTimeout(fadeTimeoutRef.current);
|
||||||
setIsVisible(false);
|
};
|
||||||
lineHeight.value = 0;
|
}, []);
|
||||||
containerOpacity.value = 0;
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
itemOpacities[i].value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hideTimeoutRef.current) {
|
// Reset when content changes
|
||||||
clearTimeout(hideTimeoutRef.current);
|
useEffect(() => {
|
||||||
hideTimeoutRef.current = null;
|
hasShownRef.current = false;
|
||||||
}
|
prevShouldShowRef.current = false;
|
||||||
if (fadeTimeoutRef.current) {
|
setWarnings([]);
|
||||||
clearTimeout(fadeTimeoutRef.current);
|
setIsVisible(false);
|
||||||
fadeTimeoutRef.current = null;
|
lineHeight.value = 0;
|
||||||
}
|
containerOpacity.value = 0;
|
||||||
}, [imdbId, season, episode]);
|
for (let i = 0; i < 5; i++) {
|
||||||
|
itemOpacities[i].value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
const containerStyle = useAnimatedStyle(() => ({
|
if (hideTimeoutRef.current) {
|
||||||
opacity: containerOpacity.value,
|
clearTimeout(hideTimeoutRef.current);
|
||||||
}));
|
hideTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
if (fadeTimeoutRef.current) {
|
||||||
|
clearTimeout(fadeTimeoutRef.current);
|
||||||
|
fadeTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
}, [imdbId, season, episode]);
|
||||||
|
|
||||||
const lineStyle = useAnimatedStyle(() => ({
|
const containerStyle = useAnimatedStyle(() => ({
|
||||||
height: lineHeight.value,
|
opacity: containerOpacity.value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!isVisible || warnings.length === 0) {
|
const lineStyle = useAnimatedStyle(() => ({
|
||||||
return null;
|
height: lineHeight.value,
|
||||||
}
|
}));
|
||||||
|
|
||||||
// Responsive sizing
|
if (!isVisible || warnings.length === 0) {
|
||||||
const fontSize = Math.min(11, screenWidth * 0.014);
|
return null;
|
||||||
const lineWidth = Math.min(3, screenWidth * 0.0038);
|
}
|
||||||
const containerPadding = Math.min(20, screenWidth * 0.025);
|
|
||||||
|
|
||||||
// Use left inset for landscape notches, top inset for portrait
|
// Responsive sizing
|
||||||
const safeLeftOffset = insets.left + containerPadding;
|
const fontSize = Math.min(11, screenWidth * 0.014);
|
||||||
const safeTopOffset = containerPadding;
|
const lineWidth = Math.min(3, screenWidth * 0.0038);
|
||||||
|
const containerPadding = Math.min(20, screenWidth * 0.025);
|
||||||
|
|
||||||
return (
|
// Use left inset for landscape notches, top inset for portrait
|
||||||
<Animated.View style={[styles.container, { left: safeLeftOffset, top: safeTopOffset }]} pointerEvents="none">
|
const safeLeftOffset = insets.left + containerPadding;
|
||||||
{/* Vertical line - animates height */}
|
const safeTopOffset = containerPadding;
|
||||||
<Animated.View style={[styles.line, lineStyle, { backgroundColor: currentTheme.colors.primary, width: lineWidth }]} />
|
|
||||||
|
|
||||||
{/* Warning items */}
|
return (
|
||||||
<View style={styles.itemsContainer}>
|
<Animated.View
|
||||||
{warnings.map((item, index) => (
|
style={[styles.container, { left: safeLeftOffset, top: safeTopOffset }]}
|
||||||
<WarningItemView
|
pointerEvents="none"
|
||||||
key={item.label}
|
>
|
||||||
item={item}
|
{/* Vertical line - animates height */}
|
||||||
opacity={itemOpacities[index]}
|
<Animated.View
|
||||||
fontSize={fontSize}
|
style={[
|
||||||
/>
|
styles.line,
|
||||||
))}
|
lineStyle,
|
||||||
</View>
|
{ backgroundColor: currentTheme.colors.primary, width: lineWidth },
|
||||||
</Animated.View>
|
]}
|
||||||
);
|
/>
|
||||||
|
|
||||||
|
{/* Warning items */}
|
||||||
|
<View style={styles.itemsContainer}>
|
||||||
|
{warnings.map((item, index) => (
|
||||||
|
<WarningItemView
|
||||||
|
key={item.label}
|
||||||
|
item={item}
|
||||||
|
opacity={itemOpacities[index]}
|
||||||
|
fontSize={fontSize}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
},
|
},
|
||||||
itemsContainer: {
|
itemsContainer: {
|
||||||
gap: 2,
|
gap: 2,
|
||||||
},
|
},
|
||||||
warningItem: {
|
warningItem: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: ROW_HEIGHT,
|
height: ROW_HEIGHT,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
color: 'rgba(255, 255, 255, 0.85)',
|
color: 'rgba(255, 255, 255, 0.85)',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
separator: {
|
separator: {
|
||||||
color: 'rgba(255, 255, 255, 0.4)',
|
color: 'rgba(255, 255, 255, 0.4)',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
marginHorizontal: 5,
|
marginHorizontal: 5,
|
||||||
},
|
},
|
||||||
severity: {
|
severity: {
|
||||||
color: 'rgba(255, 255, 255, 0.5)',
|
color: 'rgba(255, 255, 255, 0.5)',
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ParentalGuideOverlay;
|
export default ParentalGuideOverlay;
|
||||||
|
|
|
||||||
|
|
@ -1531,6 +1531,17 @@
|
||||||
"library_desc": "Save favorites, track your progress, and sync with Trakt to keep everything organized across devices.",
|
"library_desc": "Save favorites, track your progress, and sync with Trakt to keep everything organized across devices.",
|
||||||
"swipe_to_continue": "Swipe to continue",
|
"swipe_to_continue": "Swipe to continue",
|
||||||
"skip": "Skip",
|
"skip": "Skip",
|
||||||
"get_started":"Get Started"
|
"get_started": "Get Started"
|
||||||
|
},
|
||||||
|
"overlay_screen": {
|
||||||
|
"nudity": "Nudity",
|
||||||
|
"violence": "Violence",
|
||||||
|
"profanity": "Profanity",
|
||||||
|
"alcohol": "Alcohol/Drugs",
|
||||||
|
"frightening": "Frightening",
|
||||||
|
"severe":"severe",
|
||||||
|
"moderate":"moderate",
|
||||||
|
"mild":"mild",
|
||||||
|
"none":"none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1389,7 +1389,7 @@
|
||||||
"theme": {
|
"theme": {
|
||||||
"title": "Temi App",
|
"title": "Temi App",
|
||||||
"select_theme": "SELEZIONA TEMA",
|
"select_theme": "SELEZIONA TEMA",
|
||||||
"create_custom": "Crea Tema Personalizado",
|
"create_custom": "Crea Tema Personalizzato",
|
||||||
"options": "OPZIONI",
|
"options": "OPZIONI",
|
||||||
"use_dominant_color": "Usa colore dominante dall'artwork",
|
"use_dominant_color": "Usa colore dominante dall'artwork",
|
||||||
"categories": {
|
"categories": {
|
||||||
|
|
@ -1532,5 +1532,16 @@
|
||||||
"swipe_to_continue": "Scorri per proseguire",
|
"swipe_to_continue": "Scorri per proseguire",
|
||||||
"skip": "Salta",
|
"skip": "Salta",
|
||||||
"get_started": "Inizia"
|
"get_started": "Inizia"
|
||||||
|
},
|
||||||
|
"overlay_screen": {
|
||||||
|
"nudity": "Nudità",
|
||||||
|
"violence": "Violenza",
|
||||||
|
"profanity": "Volgarità",
|
||||||
|
"alcohol_drugs": "Alcol/Droga",
|
||||||
|
"frightening": "Paura",
|
||||||
|
"severe": "severa",
|
||||||
|
"moderate": "moderata",
|
||||||
|
"mild": "lieve",
|
||||||
|
"none": "nessuno"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue