mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-04 01:09:05 +00:00
PlaybackSettings IntroDB Localization Patch
This commit is contained in:
parent
46e5b469dd
commit
e3a09164e6
3 changed files with 759 additions and 604 deletions
|
|
@ -697,7 +697,8 @@
|
||||||
"media": "MEDIA",
|
"media": "MEDIA",
|
||||||
"notifications": "NOTIFICATIONS",
|
"notifications": "NOTIFICATIONS",
|
||||||
"testing": "TESTING",
|
"testing": "TESTING",
|
||||||
"danger_zone": "DANGER ZONE"
|
"danger_zone": "DANGER ZONE",
|
||||||
|
"introdb_contribution": "INTRODB CONTRIBUTION"
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"legal": "Legal & Disclaimer",
|
"legal": "Legal & Disclaimer",
|
||||||
|
|
@ -758,7 +759,12 @@
|
||||||
"reset_campaigns": "Reset Campaigns",
|
"reset_campaigns": "Reset Campaigns",
|
||||||
"reset_campaigns_desc": "Clear campaign impressions",
|
"reset_campaigns_desc": "Clear campaign impressions",
|
||||||
"clear_all_data": "Clear All Data",
|
"clear_all_data": "Clear All Data",
|
||||||
"clear_all_data_desc": "Reset all settings and cached data"
|
"clear_all_data_desc": "Reset all settings and cached data",
|
||||||
|
"enable_intro_submission": "Enable Intro Submission",
|
||||||
|
"enable_intro_submission_desc": "Contribute timestamps to the community",
|
||||||
|
"introdb_api_key": "INTRODB API KEY",
|
||||||
|
"introdb_key_placeholder": "Enter your API Key",
|
||||||
|
"api_key_cleared": "API Key Cleared"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"horizontal": "Horizontal",
|
"horizontal": "Horizontal",
|
||||||
|
|
|
||||||
|
|
@ -696,7 +696,8 @@
|
||||||
"media": "MEDIA",
|
"media": "MEDIA",
|
||||||
"notifications": "NOTIFICHE",
|
"notifications": "NOTIFICHE",
|
||||||
"testing": "TEST",
|
"testing": "TEST",
|
||||||
"danger_zone": "ZONA PERICOLOSA"
|
"danger_zone": "ZONA PERICOLOSA",
|
||||||
|
"introdb_contribution": "CONTRIBUTI INTRODB"
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"legal": "Note Legali & Disclaimer",
|
"legal": "Note Legali & Disclaimer",
|
||||||
|
|
@ -757,7 +758,12 @@
|
||||||
"reset_campaigns": "Ripristina Campagne",
|
"reset_campaigns": "Ripristina Campagne",
|
||||||
"reset_campaigns_desc": "Cancella le impressioni delle campagne",
|
"reset_campaigns_desc": "Cancella le impressioni delle campagne",
|
||||||
"clear_all_data": "Cancella tutti i dati",
|
"clear_all_data": "Cancella tutti i dati",
|
||||||
"clear_all_data_desc": "Ripristina tutte le impostazioni e i dati memorizzati"
|
"clear_all_data_desc": "Ripristina tutte le impostazioni e i dati memorizzati",
|
||||||
|
"enable_intro_submission":"Abilita contributi IntroDB",
|
||||||
|
"enable_intro_submission_desc":"Contribuisci con i tuoi timestamp",
|
||||||
|
"introdb_api_key":"CHIAVE API INTRODB",
|
||||||
|
"introdb_key_placeholder":"Inserisci la tua chiave API",
|
||||||
|
"api_key_cleared":"Chiave API rimossa"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"horizontal": "Orizzontale",
|
"horizontal": "Orizzontale",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,22 @@
|
||||||
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
import React, {
|
||||||
import { View, StyleSheet, ScrollView, StatusBar, Platform, Text, TouchableOpacity, Dimensions, TextInput, ActivityIndicator } from 'react-native';
|
useState,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollView,
|
||||||
|
StatusBar,
|
||||||
|
Platform,
|
||||||
|
Text,
|
||||||
|
TouchableOpacity,
|
||||||
|
Dimensions,
|
||||||
|
TextInput,
|
||||||
|
ActivityIndicator,
|
||||||
|
} from 'react-native';
|
||||||
import { useNavigation, useFocusEffect } from '@react-navigation/native';
|
import { useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||||
import { NavigationProp } from '@react-navigation/native';
|
import { NavigationProp } from '@react-navigation/native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
@ -7,10 +24,19 @@ import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { useSettings } from '../../hooks/useSettings';
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
import { RootStackParamList } from '../../navigation/AppNavigator';
|
import { RootStackParamList } from '../../navigation/AppNavigator';
|
||||||
import ScreenHeader from '../../components/common/ScreenHeader';
|
import ScreenHeader from '../../components/common/ScreenHeader';
|
||||||
import { SettingsCard, SettingItem, CustomSwitch, ChevronRight } from './SettingsComponents';
|
import {
|
||||||
|
SettingsCard,
|
||||||
|
SettingItem,
|
||||||
|
CustomSwitch,
|
||||||
|
ChevronRight,
|
||||||
|
} from './SettingsComponents';
|
||||||
import { useRealtimeConfig } from '../../hooks/useRealtimeConfig';
|
import { useRealtimeConfig } from '../../hooks/useRealtimeConfig';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import { BottomSheetModal, BottomSheetScrollView, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
|
import {
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetScrollView,
|
||||||
|
BottomSheetBackdrop,
|
||||||
|
} from '@gorhom/bottom-sheet';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SvgXml } from 'react-native-svg';
|
import { SvgXml } from 'react-native-svg';
|
||||||
import { toastService } from '../../services/toastService';
|
import { toastService } from '../../services/toastService';
|
||||||
|
|
@ -66,9 +92,21 @@ const AVAILABLE_LANGUAGES = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const SUBTITLE_SOURCE_OPTIONS = [
|
const SUBTITLE_SOURCE_OPTIONS = [
|
||||||
{ value: 'internal', label: 'Internal First', description: 'Prefer embedded subtitles, then external' },
|
{
|
||||||
{ value: 'external', label: 'External First', description: 'Prefer addon subtitles, then embedded' },
|
value: 'internal',
|
||||||
{ value: 'any', label: 'Any Available', description: 'Use first available subtitle track' },
|
label: 'Internal First',
|
||||||
|
description: 'Prefer embedded subtitles, then external',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'external',
|
||||||
|
label: 'External First',
|
||||||
|
description: 'Prefer addon subtitles, then embedded',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'any',
|
||||||
|
label: 'Any Available',
|
||||||
|
description: 'Use first available subtitle track',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Props for the reusable content component
|
// Props for the reusable content component
|
||||||
|
|
@ -80,7 +118,9 @@ interface PlaybackSettingsContentProps {
|
||||||
* Reusable PlaybackSettingsContent component
|
* Reusable PlaybackSettingsContent component
|
||||||
* Can be used inline (tablets) or wrapped in a screen (mobile)
|
* Can be used inline (tablets) or wrapped in a screen (mobile)
|
||||||
*/
|
*/
|
||||||
export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = ({ isTablet = false }) => {
|
export const PlaybackSettingsContent: React.FC<
|
||||||
|
PlaybackSettingsContentProps
|
||||||
|
> = ({ isTablet = false }) => {
|
||||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const { settings, updateSetting } = useSettings();
|
const { settings, updateSetting } = useSettings();
|
||||||
|
|
@ -107,7 +147,9 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
const handleApiKeySubmit = async () => {
|
const handleApiKeySubmit = async () => {
|
||||||
if (!apiKeyInput.trim()) {
|
if (!apiKeyInput.trim()) {
|
||||||
updateSetting('introDbApiKey', '');
|
updateSetting('introDbApiKey', '');
|
||||||
toastService.success(t('settings.items.api_key_cleared', { defaultValue: 'API Key Cleared' }));
|
toastService.success(
|
||||||
|
t('settings.items.api_key_cleared'),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,9 +161,13 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
updateSetting('introDbApiKey', apiKeyInput);
|
updateSetting('introDbApiKey', apiKeyInput);
|
||||||
toastService.success(t('settings.items.api_key_saved', { defaultValue: 'API Key Saved' }));
|
toastService.success(
|
||||||
|
t('settings.items.api_key_saved', { defaultValue: 'API Key Saved' }),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
toastService.error(t('settings.items.api_key_invalid', { defaultValue: 'Invalid API Key' }));
|
toastService.error(
|
||||||
|
t('settings.items.api_key_invalid', { defaultValue: 'Invalid API Key' }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -135,8 +181,14 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
// Map known classes from the IntroDB logo to equivalent inline attributes
|
// 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-4"/g, 'fill="url(#linear-gradient)"');
|
||||||
xml = xml.replace(/class="cls-3"/g, 'fill="#141414" opacity=".38"');
|
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(
|
||||||
xml = xml.replace(/class="cls-2"/g, 'fill="url(#linear-gradient-3)" opacity=".53"');
|
/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
|
// Remove the <style> block to avoid unsupported CSS
|
||||||
xml = xml.replace(/<style>[\s\S]*?<\/style>/, '');
|
xml = xml.replace(/<style>[\s\S]*?<\/style>/, '');
|
||||||
if (!cancelled) setIntroDbLogoXml(xml);
|
if (!cancelled) setIntroDbLogoXml(xml);
|
||||||
|
|
@ -153,7 +205,11 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
const introDbLogoIcon = introDbLogoXml ? (
|
const introDbLogoIcon = introDbLogoXml ? (
|
||||||
<SvgXml xml={introDbLogoXml} width={28} height={18} />
|
<SvgXml xml={introDbLogoXml} width={28} height={18} />
|
||||||
) : (
|
) : (
|
||||||
<MaterialIcons name="skip-next" size={18} color={currentTheme.colors.primary} />
|
<MaterialIcons
|
||||||
|
name="skip-next"
|
||||||
|
size={18}
|
||||||
|
color={currentTheme.colors.primary}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bottom sheet refs
|
// Bottom sheet refs
|
||||||
|
|
@ -192,11 +248,11 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasVisibleItems = (itemIds: string[]) => {
|
const hasVisibleItems = (itemIds: string[]) => {
|
||||||
return itemIds.some(id => isItemVisible(id));
|
return itemIds.some((id) => isItemVisible(id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLanguageName = (code: string) => {
|
const getLanguageName = (code: string) => {
|
||||||
const lang = AVAILABLE_LANGUAGES.find(l => l.code === code);
|
const lang = AVAILABLE_LANGUAGES.find((l) => l.code === code);
|
||||||
return lang ? lang.name : code.toUpperCase();
|
return lang ? lang.name : code.toUpperCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -217,7 +273,7 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
opacity={0.5}
|
opacity={0.5}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectAudioLanguage = (code: string) => {
|
const handleSelectAudioLanguage = (code: string) => {
|
||||||
|
|
@ -230,7 +286,9 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
subtitleLanguageSheetRef.current?.dismiss();
|
subtitleLanguageSheetRef.current?.dismiss();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectSubtitleSource = (value: 'internal' | 'external' | 'any') => {
|
const handleSelectSubtitleSource = (
|
||||||
|
value: 'internal' | 'external' | 'any',
|
||||||
|
) => {
|
||||||
updateSetting('subtitleSourcePreference', value);
|
updateSetting('subtitleSourcePreference', value);
|
||||||
subtitleSourceSheetRef.current?.dismiss();
|
subtitleSourceSheetRef.current?.dismiss();
|
||||||
};
|
};
|
||||||
|
|
@ -238,13 +296,22 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasVisibleItems(['video_player']) && (
|
{hasVisibleItems(['video_player']) && (
|
||||||
<SettingsCard title={t('settings.sections.video_player')} isTablet={isTablet}>
|
<SettingsCard
|
||||||
|
title={t('settings.sections.video_player')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
>
|
||||||
{isItemVisible('video_player') && (
|
{isItemVisible('video_player') && (
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={t('settings.items.video_player')}
|
title={t('settings.items.video_player')}
|
||||||
description={Platform.OS === 'ios'
|
description={
|
||||||
? (settings?.preferredPlayer === 'internal' ? t('settings.items.built_in') : settings?.preferredPlayer?.toUpperCase() || t('settings.items.built_in'))
|
Platform.OS === 'ios'
|
||||||
: (settings?.useExternalPlayer ? t('settings.items.external') : t('settings.items.built_in'))
|
? settings?.preferredPlayer === 'internal'
|
||||||
|
? t('settings.items.built_in')
|
||||||
|
: settings?.preferredPlayer?.toUpperCase() ||
|
||||||
|
t('settings.items.built_in')
|
||||||
|
: settings?.useExternalPlayer
|
||||||
|
? t('settings.items.external')
|
||||||
|
: t('settings.items.built_in')
|
||||||
}
|
}
|
||||||
icon="play-circle"
|
icon="play-circle"
|
||||||
renderControl={() => <ChevronRight />}
|
renderControl={() => <ChevronRight />}
|
||||||
|
|
@ -256,10 +323,17 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingsCard title={t('player.section_playback', { defaultValue: 'Playback' })} isTablet={isTablet}>
|
<SettingsCard
|
||||||
|
title={t('player.section_playback', { defaultValue: 'Playback' })}
|
||||||
|
isTablet={isTablet}
|
||||||
|
>
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={t('player.skip_intro_settings_title', { defaultValue: 'Skip Intro' })}
|
title={t('player.skip_intro_settings_title', {
|
||||||
description={t('player.powered_by_introdb', { defaultValue: 'Powered by IntroDB' })}
|
defaultValue: 'Skip Intro',
|
||||||
|
})}
|
||||||
|
description={t('player.powered_by_introdb', {
|
||||||
|
defaultValue: 'Powered by IntroDB',
|
||||||
|
})}
|
||||||
customIcon={introDbLogoIcon}
|
customIcon={introDbLogoIcon}
|
||||||
renderControl={() => (
|
renderControl={() => (
|
||||||
<CustomSwitch
|
<CustomSwitch
|
||||||
|
|
@ -273,10 +347,13 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* IntroDB Contribution Section */}
|
{/* IntroDB Contribution Section */}
|
||||||
<SettingsCard title={t('settings.sections.introdb_contribution', { defaultValue: 'IntroDB Contribution' })} isTablet={isTablet}>
|
<SettingsCard
|
||||||
|
title={t('settings.sections.introdb_contribution')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
>
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={t('settings.items.enable_intro_submission', { defaultValue: 'Enable Intro Submission' })}
|
title={t('settings.items.enable_intro_submission')}
|
||||||
description={t('settings.items.enable_intro_submission_desc', { defaultValue: 'Contribute timestamps to the community' })}
|
description={t('settings.items.enable_intro_submission_desc')}
|
||||||
icon="flag"
|
icon="flag"
|
||||||
renderControl={() => (
|
renderControl={() => (
|
||||||
<CustomSwitch
|
<CustomSwitch
|
||||||
|
|
@ -291,14 +368,17 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
{settings?.introSubmitEnabled && (
|
{settings?.introSubmitEnabled && (
|
||||||
<View style={styles.inputContainer}>
|
<View style={styles.inputContainer}>
|
||||||
<Text style={styles.inputLabel}>
|
<Text style={styles.inputLabel}>
|
||||||
{t('settings.items.introdb_api_key', { defaultValue: 'INTRODB API KEY' })}
|
{t('settings.items.introdb_api_key')}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.apiKeyRow}>
|
<View style={styles.apiKeyRow}>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { flex: 1, marginRight: 10, color: currentTheme.colors.highEmphasis }]}
|
style={[
|
||||||
|
styles.input,
|
||||||
|
{ flex: 1, marginRight: 10, color: currentTheme.colors.highEmphasis },
|
||||||
|
]}
|
||||||
value={apiKeyInput}
|
value={apiKeyInput}
|
||||||
onChangeText={setApiKeyInput}
|
onChangeText={setApiKeyInput}
|
||||||
placeholder="Enter your API key"
|
placeholder={t('settings.items.introdb_key_placeholder')}
|
||||||
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
|
|
@ -321,7 +401,10 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* Audio & Subtitle Preferences */}
|
{/* Audio & Subtitle Preferences */}
|
||||||
<SettingsCard title={t('settings.sections.audio_subtitles')} isTablet={isTablet}>
|
<SettingsCard
|
||||||
|
title={t('settings.sections.audio_subtitles')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
>
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={t('settings.items.preferred_audio')}
|
title={t('settings.items.preferred_audio')}
|
||||||
description={getLanguageName(settings?.preferredAudioLanguage || 'en')}
|
description={getLanguageName(settings?.preferredAudioLanguage || 'en')}
|
||||||
|
|
@ -340,7 +423,9 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
/>
|
/>
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={t('settings.items.subtitle_source')}
|
title={t('settings.items.subtitle_source')}
|
||||||
description={getSourceLabel(settings?.subtitleSourcePreference || 'internal')}
|
description={getSourceLabel(
|
||||||
|
settings?.subtitleSourcePreference || 'internal',
|
||||||
|
)}
|
||||||
icon="layers"
|
icon="layers"
|
||||||
renderControl={() => <ChevronRight />}
|
renderControl={() => <ChevronRight />}
|
||||||
onPress={openSubtitleSourceSheet}
|
onPress={openSubtitleSourceSheet}
|
||||||
|
|
@ -353,7 +438,9 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
renderControl={() => (
|
renderControl={() => (
|
||||||
<CustomSwitch
|
<CustomSwitch
|
||||||
value={settings?.enableSubtitleAutoSelect ?? true}
|
value={settings?.enableSubtitleAutoSelect ?? true}
|
||||||
onValueChange={(value) => updateSetting('enableSubtitleAutoSelect', value)}
|
onValueChange={(value) =>
|
||||||
|
updateSetting('enableSubtitleAutoSelect', value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
isLast
|
isLast
|
||||||
|
|
@ -396,7 +483,10 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasVisibleItems(['notifications']) && (
|
{hasVisibleItems(['notifications']) && (
|
||||||
<SettingsCard title={t('settings.sections.notifications')} isTablet={isTablet}>
|
<SettingsCard
|
||||||
|
title={t('settings.sections.notifications')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
>
|
||||||
{isItemVisible('notifications') && (
|
{isItemVisible('notifications') && (
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={t('settings.items.notifications')}
|
title={t('settings.items.notifications')}
|
||||||
|
|
@ -423,28 +513,38 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
|
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
|
||||||
>
|
>
|
||||||
<View style={styles.sheetHeader}>
|
<View style={styles.sheetHeader}>
|
||||||
<Text style={styles.sheetTitle}>{t('settings.items.preferred_audio')}</Text>
|
<Text style={styles.sheetTitle}>
|
||||||
|
{t('settings.items.preferred_audio')}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
|
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
|
||||||
{AVAILABLE_LANGUAGES.map((lang) => {
|
{AVAILABLE_LANGUAGES.map((lang) => {
|
||||||
const isSelected = lang.code === (settings?.preferredAudioLanguage || 'en');
|
const isSelected =
|
||||||
|
lang.code === (settings?.preferredAudioLanguage || 'en');
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={lang.code}
|
key={lang.code}
|
||||||
style={[
|
style={[
|
||||||
styles.languageItem,
|
styles.languageItem,
|
||||||
isSelected && { backgroundColor: currentTheme.colors.primary + '20' }
|
isSelected && { backgroundColor: currentTheme.colors.primary + '20' },
|
||||||
]}
|
]}
|
||||||
onPress={() => handleSelectAudioLanguage(lang.code)}
|
onPress={() => handleSelectAudioLanguage(lang.code)}
|
||||||
>
|
>
|
||||||
<Text style={[styles.languageName, { color: isSelected ? currentTheme.colors.primary : '#fff' }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.languageName,
|
||||||
|
{ color: isSelected ? currentTheme.colors.primary : '#fff' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{lang.name}
|
{lang.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.languageCode}>
|
<Text style={styles.languageCode}>{lang.code.toUpperCase()}</Text>
|
||||||
{lang.code.toUpperCase()}
|
|
||||||
</Text>
|
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<MaterialIcons name="check" size={20} color={currentTheme.colors.primary} />
|
<MaterialIcons
|
||||||
|
name="check"
|
||||||
|
size={20}
|
||||||
|
color={currentTheme.colors.primary}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
@ -464,28 +564,38 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
|
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
|
||||||
>
|
>
|
||||||
<View style={styles.sheetHeader}>
|
<View style={styles.sheetHeader}>
|
||||||
<Text style={styles.sheetTitle}>{t('settings.items.preferred_subtitle')}</Text>
|
<Text style={styles.sheetTitle}>
|
||||||
|
{t('settings.items.preferred_subtitle')}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
|
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
|
||||||
{AVAILABLE_LANGUAGES.map((lang) => {
|
{AVAILABLE_LANGUAGES.map((lang) => {
|
||||||
const isSelected = lang.code === (settings?.preferredSubtitleLanguage || 'en');
|
const isSelected =
|
||||||
|
lang.code === (settings?.preferredSubtitleLanguage || 'en');
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={lang.code}
|
key={lang.code}
|
||||||
style={[
|
style={[
|
||||||
styles.languageItem,
|
styles.languageItem,
|
||||||
isSelected && { backgroundColor: currentTheme.colors.primary + '20' }
|
isSelected && { backgroundColor: currentTheme.colors.primary + '20' },
|
||||||
]}
|
]}
|
||||||
onPress={() => handleSelectSubtitleLanguage(lang.code)}
|
onPress={() => handleSelectSubtitleLanguage(lang.code)}
|
||||||
>
|
>
|
||||||
<Text style={[styles.languageName, { color: isSelected ? currentTheme.colors.primary : '#fff' }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.languageName,
|
||||||
|
{ color: isSelected ? currentTheme.colors.primary : '#fff' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{lang.name}
|
{lang.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.languageCode}>
|
<Text style={styles.languageCode}>{lang.code.toUpperCase()}</Text>
|
||||||
{lang.code.toUpperCase()}
|
|
||||||
</Text>
|
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<MaterialIcons name="check" size={20} color={currentTheme.colors.primary} />
|
<MaterialIcons
|
||||||
|
name="check"
|
||||||
|
size={20}
|
||||||
|
color={currentTheme.colors.primary}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
@ -505,32 +615,53 @@ export const PlaybackSettingsContent: React.FC<PlaybackSettingsContentProps> = (
|
||||||
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
|
handleIndicatorStyle={{ backgroundColor: 'rgba(255,255,255,0.3)' }}
|
||||||
>
|
>
|
||||||
<View style={styles.sheetHeader}>
|
<View style={styles.sheetHeader}>
|
||||||
<Text style={styles.sheetTitle}>{t('settings.items.subtitle_source')}</Text>
|
<Text style={styles.sheetTitle}>
|
||||||
|
{t('settings.items.subtitle_source')}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
|
<BottomSheetScrollView contentContainerStyle={styles.sheetContent}>
|
||||||
{SUBTITLE_SOURCE_OPTIONS.map((option) => {
|
{SUBTITLE_SOURCE_OPTIONS.map((option) => {
|
||||||
const isSelected = option.value === (settings?.subtitleSourcePreference || 'internal');
|
const isSelected =
|
||||||
|
option.value === (settings?.subtitleSourcePreference || 'internal');
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={option.value}
|
key={option.value}
|
||||||
style={[
|
style={[
|
||||||
styles.sourceItem,
|
styles.sourceItem,
|
||||||
isSelected && { backgroundColor: currentTheme.colors.primary + '20', borderColor: currentTheme.colors.primary }
|
isSelected && {
|
||||||
|
backgroundColor: currentTheme.colors.primary + '20',
|
||||||
|
borderColor: currentTheme.colors.primary,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
onPress={() => handleSelectSubtitleSource(option.value as 'internal' | 'external' | 'any')}
|
onPress={() =>
|
||||||
|
handleSelectSubtitleSource(
|
||||||
|
option.value as 'internal' | 'external' | 'any',
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<View style={styles.sourceItemContent}>
|
<View style={styles.sourceItemContent}>
|
||||||
<Text style={[styles.sourceLabel, { color: isSelected ? currentTheme.colors.primary : '#fff' }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.sourceLabel,
|
||||||
|
{ color: isSelected ? currentTheme.colors.primary : '#fff' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{getSourceLabel(option.value)}
|
{getSourceLabel(option.value)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.sourceDescription}>
|
<Text style={styles.sourceDescription}>
|
||||||
{option.value === 'internal' && t('settings.options.internal_first_desc')}
|
{option.value === 'internal' &&
|
||||||
{option.value === 'external' && t('settings.options.external_first_desc')}
|
t('settings.options.internal_first_desc')}
|
||||||
|
{option.value === 'external' &&
|
||||||
|
t('settings.options.external_first_desc')}
|
||||||
{option.value === 'any' && t('settings.options.any_available_desc')}
|
{option.value === 'any' && t('settings.options.any_available_desc')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<MaterialIcons name="check" size={20} color={currentTheme.colors.primary} />
|
<MaterialIcons
|
||||||
|
name="check"
|
||||||
|
size={20}
|
||||||
|
color={currentTheme.colors.primary}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
@ -553,14 +684,26 @@ const PlaybackSettingsScreen: React.FC = () => {
|
||||||
const screenIsTablet = width >= 768;
|
const screenIsTablet = width >= 768;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
<View
|
||||||
|
style={[
|
||||||
|
styles.container,
|
||||||
|
{ backgroundColor: currentTheme.colors.darkBackground },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<StatusBar barStyle="light-content" />
|
<StatusBar barStyle="light-content" />
|
||||||
<ScreenHeader title={t('settings.playback')} showBackButton onBackPress={() => navigation.goBack()} />
|
<ScreenHeader
|
||||||
|
title={t('settings.playback')}
|
||||||
|
showBackButton
|
||||||
|
onBackPress={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
|
contentContainerStyle={[
|
||||||
|
styles.scrollContent,
|
||||||
|
{ paddingBottom: insets.bottom + 24 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<PlaybackSettingsContent isTablet={screenIsTablet} />
|
<PlaybackSettingsContent isTablet={screenIsTablet} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue