PlaybackSettings IntroDB Localization Patch

This commit is contained in:
cyberalby2 2026-03-06 19:55:44 +01:00
parent 46e5b469dd
commit e3a09164e6
3 changed files with 759 additions and 604 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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>