added skip intro toggle

This commit is contained in:
tapframe 2026-01-12 19:29:39 +05:30
parent 0ddc78587b
commit 61a69799d4
3 changed files with 72 additions and 2 deletions

View file

@ -13,6 +13,7 @@ import { BlurView } from 'expo-blur';
import { introService, SkipInterval, SkipType } from '../../../services/introService';
import { useTheme } from '../../../contexts/ThemeContext';
import { logger } from '../../../utils/logger';
import { useSettings } from '../../../hooks/useSettings';
interface SkipIntroButtonProps {
imdbId: string | undefined;
@ -40,8 +41,11 @@ export const SkipIntroButton: React.FC<SkipIntroButtonProps> = ({
controlsFixedOffset = 100,
}) => {
const { currentTheme } = useTheme();
const { settings } = useSettings();
const insets = useSafeAreaInsets();
const skipIntroEnabled = settings.skipIntroEnabled;
// State
const [skipIntervals, setSkipIntervals] = useState<SkipInterval[]>([]);
const [currentInterval, setCurrentInterval] = useState<SkipInterval | null>(null);
@ -63,6 +67,14 @@ export const SkipIntroButton: React.FC<SkipIntroButtonProps> = ({
useEffect(() => {
const episodeKey = `${imdbId}-${season}-${episode}-${malId}-${kitsuId}`;
if (!skipIntroEnabled) {
setSkipIntervals([]);
setCurrentInterval(null);
setIsVisible(false);
fetchedRef.current = false;
return;
}
// Skip if not a series or missing required data (though MAL/Kitsu ID might be enough for some cases, usually need season/ep)
if (type !== 'series' || (!imdbId && !malId && !kitsuId) || !season || !episode) {
setSkipIntervals([]);
@ -99,7 +111,7 @@ export const SkipIntroButton: React.FC<SkipIntroButtonProps> = ({
};
fetchSkipData();
}, [imdbId, type, season, episode, malId, kitsuId]);
}, [imdbId, type, season, episode, malId, kitsuId, skipIntroEnabled]);
// Determine active interval based on current playback position
useEffect(() => {
@ -228,6 +240,10 @@ export const SkipIntroButton: React.FC<SkipIntroButtonProps> = ({
transform: [{ scale: scale.value }, { translateY: translateY.value }],
}));
if (!skipIntroEnabled) {
return null;
}
// Don't render if not visible
if (!isVisible) {
return null;

View file

@ -59,6 +59,7 @@ export interface AppSettings {
excludedLanguages: string[]; // Array of language strings to exclude (e.g., ['Spanish', 'German', 'French'])
// Playback behavior
alwaysResume: boolean; // If true, resume automatically without prompt when progress < 85%
skipIntroEnabled: boolean; // Enable/disable Skip Intro overlay (IntroDB)
// Downloads
enableDownloads: boolean; // Show Downloads tab and enable saving streams
// Theme settings
@ -147,6 +148,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
excludedLanguages: [], // No languages excluded by default
// Playback behavior defaults
alwaysResume: true,
skipIntroEnabled: true,
// Downloads
enableDownloads: false,
useExternalPlayerForDownloads: false,

View file

@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo, useRef } from 'react';
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
import { View, StyleSheet, ScrollView, StatusBar, Platform, Text, TouchableOpacity, Dimensions } from 'react-native';
import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
@ -12,9 +12,12 @@ import { useRealtimeConfig } from '../../hooks/useRealtimeConfig';
import { MaterialIcons } from '@expo/vector-icons';
import { BottomSheetModal, BottomSheetScrollView, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
import { useTranslation } from 'react-i18next';
import { SvgXml } from 'react-native-svg';
const { width } = Dimensions.get('window');
const INTRODB_LOGO_URI = 'https://introdb.app/images/logo-vector.svg';
// Available languages for audio/subtitle selection
const AVAILABLE_LANGUAGES = [
{ code: 'en', name: 'English' },
@ -73,6 +76,39 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
const { t } = useTranslation();
const config = useRealtimeConfig();
const [introDbLogoXml, setIntroDbLogoXml] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
const load = async () => {
try {
const res = await fetch(INTRODB_LOGO_URI);
let xml = await res.text();
// Inline CSS class-based styles because react-native-svg doesn't support <style> class selectors
// Map known classes from the IntroDB logo to equivalent inline attributes
xml = xml.replace(/class="cls-4"/g, 'fill="url(#linear-gradient)"');
xml = xml.replace(/class="cls-3"/g, 'fill="#141414" opacity=".38"');
xml = xml.replace(/class="cls-1"/g, 'fill="url(#linear-gradient-2)" opacity=".53"');
xml = xml.replace(/class="cls-2"/g, 'fill="url(#linear-gradient-3)" opacity=".53"');
// Remove the <style> block to avoid unsupported CSS
xml = xml.replace(/<style>[\s\S]*?<\/style>/, '');
if (!cancelled) setIntroDbLogoXml(xml);
} catch {
if (!cancelled) setIntroDbLogoXml(null);
}
};
load();
return () => {
cancelled = true;
};
}, []);
const introDbLogoIcon = introDbLogoXml ? (
<SvgXml xml={introDbLogoXml} width={28} height={18} />
) : (
<MaterialIcons name="skip-next" size={18} color={currentTheme.colors.primary} />
);
// Bottom sheet refs
const audioLanguageSheetRef = useRef<BottomSheetModal>(null);
const subtitleLanguageSheetRef = useRef<BottomSheetModal>(null);
@ -173,6 +209,22 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
</SettingsCard>
)}
<SettingsCard title={t('player.section_playback', { defaultValue: 'Playback' })} isTablet={isTablet}>
<SettingItem
title={t('player.skip_intro_settings_title', { defaultValue: 'Skip Intro' })}
description={t('player.powered_by_introdb', { defaultValue: 'Powered by IntroDB' })}
customIcon={introDbLogoIcon}
renderControl={() => (
<CustomSwitch
value={settings?.skipIntroEnabled ?? true}
onValueChange={(value) => updateSetting('skipIntroEnabled', value)}
/>
)}
isLast
isTablet={isTablet}
/>
</SettingsCard>
{/* Audio & Subtitle Preferences */}
<SettingsCard title={t('settings.sections.audio_subtitles')} isTablet={isTablet}>
<SettingItem