mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-20 00:02:03 +00:00
remove unecessary blur on adnroid
This commit is contained in:
parent
9a446f5a45
commit
da4e4031bf
5 changed files with 32 additions and 290 deletions
|
|
@ -179,17 +179,7 @@ const ActionButtons = React.memo(({
|
||||||
{Platform.OS === 'ios' ? (
|
{Platform.OS === 'ios' ? (
|
||||||
<ExpoBlurView intensity={80} style={styles.blurBackground} tint="dark" />
|
<ExpoBlurView intensity={80} style={styles.blurBackground} tint="dark" />
|
||||||
) : (
|
) : (
|
||||||
Constants.executionEnvironment === ExecutionEnvironment.StoreClient ? (
|
<View style={styles.androidFallbackBlur} />
|
||||||
<View style={styles.androidFallbackBlur} />
|
|
||||||
) : (
|
|
||||||
<CommunityBlurView
|
|
||||||
style={styles.blurBackground}
|
|
||||||
blurType="dark"
|
|
||||||
blurAmount={8}
|
|
||||||
overlayColor="rgba(255,255,255,0.1)"
|
|
||||||
reducedTransparencyFallbackColor="rgba(255,255,255,0.15)"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
name={inLibrary ? 'bookmark' : 'bookmark-border'}
|
||||||
|
|
@ -210,17 +200,7 @@ const ActionButtons = React.memo(({
|
||||||
{Platform.OS === 'ios' ? (
|
{Platform.OS === 'ios' ? (
|
||||||
<ExpoBlurView intensity={80} style={styles.blurBackgroundRound} tint="dark" />
|
<ExpoBlurView intensity={80} style={styles.blurBackgroundRound} tint="dark" />
|
||||||
) : (
|
) : (
|
||||||
Constants.executionEnvironment === ExecutionEnvironment.StoreClient ? (
|
<View style={styles.androidFallbackBlurRound} />
|
||||||
<View style={styles.androidFallbackBlurRound} />
|
|
||||||
) : (
|
|
||||||
<CommunityBlurView
|
|
||||||
style={styles.blurBackgroundRound}
|
|
||||||
blurType="dark"
|
|
||||||
blurAmount={8}
|
|
||||||
overlayColor="rgba(255,255,255,0.1)"
|
|
||||||
reducedTransparencyFallbackColor="rgba(255,255,255,0.15)"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name="assessment"
|
name="assessment"
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
|
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
const [isSyncingBeforeClose, setIsSyncingBeforeClose] = useState(false);
|
const [isSyncingBeforeClose, setIsSyncingBeforeClose] = useState(false);
|
||||||
|
// Offset in seconds to avoid seeking to the exact end, which fires onEnd and resets.
|
||||||
|
const END_EPSILON = 0.3;
|
||||||
|
|
||||||
const hideControls = () => {
|
const hideControls = () => {
|
||||||
Animated.timing(fadeAnim, {
|
Animated.timing(fadeAnim, {
|
||||||
|
|
@ -355,7 +357,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
};
|
};
|
||||||
}, [id, type, currentTime, duration]);
|
}, [id, type, currentTime, duration]);
|
||||||
|
|
||||||
const seekToTime = (timeInSeconds: number) => {
|
const seekToTime = (rawSeconds: number) => {
|
||||||
|
// Clamp to just before the end of the media.
|
||||||
|
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
|
||||||
if (videoRef.current && duration > 0 && !isSeeking.current) {
|
if (videoRef.current && duration > 0 && !isSeeking.current) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
logger.log(`[AndroidVideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
logger.log(`[AndroidVideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
||||||
|
|
@ -415,8 +419,8 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
const processProgressTouch = (locationX: number, isDragging = false) => {
|
const processProgressTouch = (locationX: number, isDragging = false) => {
|
||||||
progressBarRef.current?.measure((x, y, width, height, pageX, pageY) => {
|
progressBarRef.current?.measure((x, y, width, height, pageX, pageY) => {
|
||||||
const percentage = Math.max(0, Math.min(locationX / width, 1));
|
const percentage = Math.max(0, Math.min(locationX / width, 0.999));
|
||||||
const seekTime = percentage * duration;
|
const seekTime = Math.min(percentage * duration, duration - END_EPSILON);
|
||||||
progressAnim.setValue(percentage);
|
progressAnim.setValue(percentage);
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
pendingSeekValue.current = seekTime;
|
pendingSeekValue.current = seekTime;
|
||||||
|
|
@ -519,7 +523,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
const skip = (seconds: number) => {
|
const skip = (seconds: number) => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
const newTime = Math.max(0, Math.min(currentTime + seconds, duration));
|
const newTime = Math.max(0, Math.min(currentTime + seconds, duration - END_EPSILON));
|
||||||
seekToTime(newTime);
|
seekToTime(newTime);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,9 @@ const VideoPlayer: React.FC = () => {
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
|
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
const [isSyncingBeforeClose, setIsSyncingBeforeClose] = useState(false);
|
const [isSyncingBeforeClose, setIsSyncingBeforeClose] = useState(false);
|
||||||
|
// Small offset (in seconds) used to avoid seeking to the *exact* end of the
|
||||||
|
// file which triggers the `onEnd` callback and causes playback to restart.
|
||||||
|
const END_EPSILON = 0.3;
|
||||||
|
|
||||||
const hideControls = () => {
|
const hideControls = () => {
|
||||||
Animated.timing(fadeAnim, {
|
Animated.timing(fadeAnim, {
|
||||||
|
|
@ -371,7 +374,9 @@ const VideoPlayer: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const seekToTime = (timeInSeconds: number) => {
|
const seekToTime = (rawSeconds: number) => {
|
||||||
|
// Clamp to just before the end to avoid triggering onEnd.
|
||||||
|
const timeInSeconds = Math.max(0, Math.min(rawSeconds, duration > 0 ? duration - END_EPSILON : rawSeconds));
|
||||||
if (vlcRef.current && duration > 0 && !isSeeking.current) {
|
if (vlcRef.current && duration > 0 && !isSeeking.current) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
logger.log(`[VideoPlayer] Seeking to ${timeInSeconds.toFixed(2)}s out of ${duration.toFixed(2)}s`);
|
||||||
|
|
@ -443,8 +448,8 @@ const VideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
const processProgressTouch = (locationX: number, isDragging = false) => {
|
const processProgressTouch = (locationX: number, isDragging = false) => {
|
||||||
progressBarRef.current?.measure((x, y, width, height, pageX, pageY) => {
|
progressBarRef.current?.measure((x, y, width, height, pageX, pageY) => {
|
||||||
const percentage = Math.max(0, Math.min(locationX / width, 1));
|
const percentage = Math.max(0, Math.min(locationX / width, 0.999));
|
||||||
const seekTime = percentage * duration;
|
const seekTime = Math.min(percentage * duration, duration - END_EPSILON);
|
||||||
progressAnim.setValue(percentage);
|
progressAnim.setValue(percentage);
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
pendingSeekValue.current = seekTime;
|
pendingSeekValue.current = seekTime;
|
||||||
|
|
@ -530,7 +535,7 @@ const VideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
const skip = (seconds: number) => {
|
const skip = (seconds: number) => {
|
||||||
if (vlcRef.current) {
|
if (vlcRef.current) {
|
||||||
const newTime = Math.max(0, Math.min(currentTime + seconds, duration));
|
const newTime = Math.max(0, Math.min(currentTime + seconds, duration - END_EPSILON));
|
||||||
seekToTime(newTime);
|
seekToTime(newTime);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -462,70 +462,6 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
padding: 6,
|
padding: 6,
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
},
|
},
|
||||||
communityAddonsList: {
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
},
|
|
||||||
communityAddonItem: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: colors.card,
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 15,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
communityAddonIcon: {
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
borderRadius: 6,
|
|
||||||
marginRight: 15,
|
|
||||||
},
|
|
||||||
communityAddonIconPlaceholder: {
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
borderRadius: 6,
|
|
||||||
marginRight: 15,
|
|
||||||
backgroundColor: colors.darkGray,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
communityAddonDetails: {
|
|
||||||
flex: 1,
|
|
||||||
marginRight: 10,
|
|
||||||
},
|
|
||||||
communityAddonName: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: colors.white,
|
|
||||||
marginBottom: 3,
|
|
||||||
},
|
|
||||||
communityAddonDesc: {
|
|
||||||
fontSize: 13,
|
|
||||||
color: colors.lightGray,
|
|
||||||
marginBottom: 5,
|
|
||||||
opacity: 0.9,
|
|
||||||
},
|
|
||||||
communityAddonMetaContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
opacity: 0.8,
|
|
||||||
},
|
|
||||||
communityAddonVersion: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: colors.lightGray,
|
|
||||||
},
|
|
||||||
communityAddonDot: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: colors.lightGray,
|
|
||||||
marginHorizontal: 5,
|
|
||||||
},
|
|
||||||
communityAddonCategory: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: colors.lightGray,
|
|
||||||
flexShrink: 1,
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
sectionSeparator: {
|
sectionSeparator: {
|
||||||
height: 1,
|
height: 1,
|
||||||
backgroundColor: colors.border,
|
backgroundColor: colors.border,
|
||||||
|
|
@ -560,7 +496,7 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
backgroundColor: 'black',
|
||||||
},
|
},
|
||||||
androidBlurContainer: {
|
androidBlurContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
@ -618,14 +554,8 @@ const AddonsScreen = () => {
|
||||||
const colors = currentTheme.colors;
|
const colors = currentTheme.colors;
|
||||||
const styles = createStyles(colors);
|
const styles = createStyles(colors);
|
||||||
|
|
||||||
// State for community addons
|
|
||||||
const [communityAddons, setCommunityAddons] = useState<CommunityAddon[]>([]);
|
|
||||||
const [communityLoading, setCommunityLoading] = useState(true);
|
|
||||||
const [communityError, setCommunityError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAddons();
|
loadAddons();
|
||||||
loadCommunityAddons();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadAddons = async () => {
|
const loadAddons = async () => {
|
||||||
|
|
@ -662,34 +592,10 @@ const AddonsScreen = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to load community addons
|
|
||||||
const loadCommunityAddons = async () => {
|
|
||||||
setCommunityLoading(true);
|
|
||||||
setCommunityError(null);
|
|
||||||
try {
|
|
||||||
const response = await axios.get<CommunityAddon[]>('https://stremio-addons.com/catalog.json');
|
|
||||||
// Filter out addons without a manifest or transportUrl (basic validation)
|
|
||||||
let validAddons = response.data.filter(addon => addon.manifest && addon.transportUrl);
|
|
||||||
|
|
||||||
// Filter out Cinemeta if it's already in the community list to avoid duplication
|
|
||||||
validAddons = validAddons.filter(addon => addon.manifest.id !== 'com.linvo.cinemeta');
|
|
||||||
|
|
||||||
// Add Cinemeta to the beginning of the list
|
|
||||||
setCommunityAddons([cinemetaAddon, ...validAddons]);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to load community addons:', error);
|
|
||||||
setCommunityError('Failed to load community addons. Please try again later.');
|
|
||||||
// Still show Cinemeta if the community list fails to load
|
|
||||||
setCommunityAddons([cinemetaAddon]);
|
|
||||||
} finally {
|
|
||||||
setCommunityLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddAddon = async (url?: string) => {
|
const handleAddAddon = async (url?: string) => {
|
||||||
const urlToInstall = url || addonUrl;
|
const urlToInstall = url || addonUrl;
|
||||||
if (!urlToInstall) {
|
if (!urlToInstall) {
|
||||||
Alert.alert('Error', 'Please enter an addon URL or select a community addon');
|
Alert.alert('Error', 'Please enter an addon URL');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -728,7 +634,6 @@ const AddonsScreen = () => {
|
||||||
|
|
||||||
const refreshAddons = async () => {
|
const refreshAddons = async () => {
|
||||||
loadAddons();
|
loadAddons();
|
||||||
loadCommunityAddons();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveAddonUp = (addon: ExtendedManifest) => {
|
const moveAddonUp = (addon: ExtendedManifest) => {
|
||||||
|
|
@ -991,66 +896,6 @@ const AddonsScreen = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to render community addon items
|
|
||||||
const renderCommunityAddonItem = ({ item }: { item: CommunityAddon }) => {
|
|
||||||
const { manifest, transportUrl } = item;
|
|
||||||
const types = manifest.types || [];
|
|
||||||
const description = manifest.description || 'No description provided.';
|
|
||||||
// @ts-ignore - logo might exist
|
|
||||||
const logo = manifest.logo || null;
|
|
||||||
const categoryText = types.length > 0
|
|
||||||
? types.map(t => t.charAt(0).toUpperCase() + t.slice(1)).join(' • ')
|
|
||||||
: 'General';
|
|
||||||
// Check if addon is configurable
|
|
||||||
const isConfigurable = manifest.behaviorHints?.configurable === true;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.communityAddonItem}>
|
|
||||||
{logo ? (
|
|
||||||
<ExpoImage
|
|
||||||
source={{ uri: logo }}
|
|
||||||
style={styles.communityAddonIcon}
|
|
||||||
contentFit="contain"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<View style={styles.communityAddonIconPlaceholder}>
|
|
||||||
<MaterialIcons name="extension" size={22} color={colors.darkGray} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View style={styles.communityAddonDetails}>
|
|
||||||
<Text style={styles.communityAddonName}>{manifest.name}</Text>
|
|
||||||
<Text style={styles.communityAddonDesc} numberOfLines={2}>{description}</Text>
|
|
||||||
<View style={styles.communityAddonMetaContainer}>
|
|
||||||
<Text style={styles.communityAddonVersion}>v{manifest.version || 'N/A'}</Text>
|
|
||||||
<Text style={styles.communityAddonDot}>•</Text>
|
|
||||||
<Text style={styles.communityAddonCategory}>{categoryText}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={styles.addonActionButtons}>
|
|
||||||
{isConfigurable && (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.configButton}
|
|
||||||
onPress={() => handleConfigureAddon(manifest, transportUrl)}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="settings" size={20} color={colors.primary} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.installButton, installing && { opacity: 0.6 }]}
|
|
||||||
onPress={() => handleAddAddon(transportUrl)}
|
|
||||||
disabled={installing}
|
|
||||||
>
|
|
||||||
{installing ? (
|
|
||||||
<ActivityIndicator size="small" color={colors.white} />
|
|
||||||
) : (
|
|
||||||
<MaterialIcons name="add" size={20} color={colors.white} />
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatsCard = ({ value, label }: { value: number; label: string }) => (
|
const StatsCard = ({ value, label }: { value: number; label: string }) => (
|
||||||
<View style={styles.statsCard}>
|
<View style={styles.statsCard}>
|
||||||
<Text style={styles.statsValue}>{value}</Text>
|
<Text style={styles.statsValue}>{value}</Text>
|
||||||
|
|
@ -1186,95 +1031,6 @@ const AddonsScreen = () => {
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Separator */}
|
|
||||||
<View style={styles.sectionSeparator} />
|
|
||||||
|
|
||||||
{/* Community Addons Section */}
|
|
||||||
<View style={styles.section}>
|
|
||||||
<Text style={styles.sectionTitle}>COMMUNITY ADDONS</Text>
|
|
||||||
<View style={styles.addonList}>
|
|
||||||
{communityLoading ? (
|
|
||||||
<View style={styles.loadingContainer}>
|
|
||||||
<ActivityIndicator size="large" color={colors.primary} />
|
|
||||||
</View>
|
|
||||||
) : communityError ? (
|
|
||||||
<View style={styles.emptyContainer}>
|
|
||||||
<MaterialIcons name="error-outline" size={32} color={colors.error} />
|
|
||||||
<Text style={styles.emptyText}>{communityError}</Text>
|
|
||||||
</View>
|
|
||||||
) : communityAddons.length === 0 ? (
|
|
||||||
<View style={styles.emptyContainer}>
|
|
||||||
<MaterialIcons name="extension-off" size={32} color={colors.mediumGray} />
|
|
||||||
<Text style={styles.emptyText}>No community addons available</Text>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
communityAddons.map((item, index) => (
|
|
||||||
<View
|
|
||||||
key={item.transportUrl}
|
|
||||||
style={{ marginBottom: index === communityAddons.length - 1 ? 32 : 16 }}
|
|
||||||
>
|
|
||||||
<View style={styles.addonItem}>
|
|
||||||
<View style={styles.addonHeader}>
|
|
||||||
{item.manifest.logo ? (
|
|
||||||
<ExpoImage
|
|
||||||
source={{ uri: item.manifest.logo }}
|
|
||||||
style={styles.addonIcon}
|
|
||||||
contentFit="contain"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<View style={styles.addonIconPlaceholder}>
|
|
||||||
<MaterialIcons name="extension" size={22} color={colors.mediumGray} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View style={styles.addonTitleContainer}>
|
|
||||||
<Text style={styles.addonName}>{item.manifest.name}</Text>
|
|
||||||
<View style={styles.addonMetaContainer}>
|
|
||||||
<Text style={styles.addonVersion}>v{item.manifest.version || 'N/A'}</Text>
|
|
||||||
<Text style={styles.addonDot}>•</Text>
|
|
||||||
<Text style={styles.addonCategory}>
|
|
||||||
{item.manifest.types && item.manifest.types.length > 0
|
|
||||||
? item.manifest.types.map(t => t.charAt(0).toUpperCase() + t.slice(1)).join(' • ')
|
|
||||||
: 'General'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={styles.addonActions}>
|
|
||||||
{item.manifest.behaviorHints?.configurable && (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.configButton}
|
|
||||||
onPress={() => handleConfigureAddon(item.manifest, item.transportUrl)}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="settings" size={20} color={colors.primary} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.installButton, installing && { opacity: 0.6 }]}
|
|
||||||
onPress={() => handleAddAddon(item.transportUrl)}
|
|
||||||
disabled={installing}
|
|
||||||
>
|
|
||||||
{installing ? (
|
|
||||||
<ActivityIndicator size="small" color={colors.white} />
|
|
||||||
) : (
|
|
||||||
<MaterialIcons name="add" size={20} color={colors.white} />
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text style={styles.addonDescription}>
|
|
||||||
{item.manifest.description
|
|
||||||
? (item.manifest.description.length > 100
|
|
||||||
? item.manifest.description.substring(0, 100) + '...'
|
|
||||||
: item.manifest.description)
|
|
||||||
: 'No description provided.'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import { useTheme } from '../contexts/ThemeContext';
|
||||||
import { catalogService } from '../services/catalogService';
|
import { catalogService } from '../services/catalogService';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import * as Sentry from '@sentry/react-native';
|
import * as Sentry from '@sentry/react-native';
|
||||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
const { width } = Dimensions.get('window');
|
||||||
|
|
||||||
|
|
@ -39,15 +38,13 @@ const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||||
interface SettingsCardProps {
|
interface SettingsCardProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
delay?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title, delay = 0 }) => {
|
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title }) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<View
|
||||||
entering={FadeInDown.delay(delay).duration(400)}
|
|
||||||
style={[styles.cardContainer]}
|
style={[styles.cardContainer]}
|
||||||
>
|
>
|
||||||
{title && (
|
{title && (
|
||||||
|
|
@ -64,7 +61,7 @@ const SettingsCard: React.FC<SettingsCardProps> = ({ children, title, delay = 0
|
||||||
]}>
|
]}>
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -297,7 +294,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
>
|
>
|
||||||
{/* Account Section */}
|
{/* Account Section */}
|
||||||
<SettingsCard title="ACCOUNT" delay={0}>
|
<SettingsCard title="ACCOUNT">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Trakt"
|
title="Trakt"
|
||||||
description={isAuthenticated ? `@${userProfile?.username || 'User'}` : "Sign in to sync"}
|
description={isAuthenticated ? `@${userProfile?.username || 'User'}` : "Sign in to sync"}
|
||||||
|
|
@ -318,7 +315,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* Content & Discovery */}
|
{/* Content & Discovery */}
|
||||||
<SettingsCard title="CONTENT & DISCOVERY" delay={100}>
|
<SettingsCard title="CONTENT & DISCOVERY">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Addons"
|
title="Addons"
|
||||||
description={`${addonCount} installed`}
|
description={`${addonCount} installed`}
|
||||||
|
|
@ -344,7 +341,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* Appearance & Interface */}
|
{/* Appearance & Interface */}
|
||||||
<SettingsCard title="APPEARANCE" delay={200}>
|
<SettingsCard title="APPEARANCE">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Theme"
|
title="Theme"
|
||||||
description={currentTheme.name}
|
description={currentTheme.name}
|
||||||
|
|
@ -367,7 +364,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* Integrations */}
|
{/* Integrations */}
|
||||||
<SettingsCard title="INTEGRATIONS" delay={300}>
|
<SettingsCard title="INTEGRATIONS">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="MDBList"
|
title="MDBList"
|
||||||
description={mdblistKeySet ? "Connected" : "Enable to add ratings & reviews"}
|
description={mdblistKeySet ? "Connected" : "Enable to add ratings & reviews"}
|
||||||
|
|
@ -393,7 +390,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* Playback & Experience */}
|
{/* Playback & Experience */}
|
||||||
<SettingsCard title="PLAYBACK" delay={400}>
|
<SettingsCard title="PLAYBACK">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Video Player"
|
title="Video Player"
|
||||||
description={Platform.OS === 'ios'
|
description={Platform.OS === 'ios'
|
||||||
|
|
@ -422,7 +419,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
|
|
||||||
{/* About & Support */}
|
{/* About & Support */}
|
||||||
<SettingsCard title="ABOUT" delay={500}>
|
<SettingsCard title="ABOUT">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Privacy Policy"
|
title="Privacy Policy"
|
||||||
icon="lock"
|
icon="lock"
|
||||||
|
|
@ -445,7 +442,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
|
|
||||||
{/* Developer Options - Only show in development */}
|
{/* Developer Options - Only show in development */}
|
||||||
{__DEV__ && (
|
{__DEV__ && (
|
||||||
<SettingsCard title="DEVELOPER" delay={600}>
|
<SettingsCard title="DEVELOPER">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Test Onboarding"
|
title="Test Onboarding"
|
||||||
icon="play-circle-outline"
|
icon="play-circle-outline"
|
||||||
|
|
@ -496,7 +493,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
|
|
||||||
{/* Cache Management - Only show if MDBList is connected */}
|
{/* Cache Management - Only show if MDBList is connected */}
|
||||||
{mdblistKeySet && (
|
{mdblistKeySet && (
|
||||||
<SettingsCard title="CACHE MANAGEMENT" delay={600}>
|
<SettingsCard title="CACHE MANAGEMENT">
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Clear MDBList Cache"
|
title="Clear MDBList Cache"
|
||||||
icon="cached"
|
icon="cached"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue