remove unecessary blur on adnroid

This commit is contained in:
tapframe 2025-07-05 12:34:18 +05:30
parent 9a446f5a45
commit da4e4031bf
5 changed files with 32 additions and 290 deletions

View file

@ -179,17 +179,7 @@ const ActionButtons = React.memo(({
{Platform.OS === 'ios' ? (
<ExpoBlurView intensity={80} style={styles.blurBackground} tint="dark" />
) : (
Constants.executionEnvironment === ExecutionEnvironment.StoreClient ? (
<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)"
/>
)
<View style={styles.androidFallbackBlur} />
)}
<MaterialIcons
name={inLibrary ? 'bookmark' : 'bookmark-border'}
@ -210,17 +200,7 @@ const ActionButtons = React.memo(({
{Platform.OS === 'ios' ? (
<ExpoBlurView intensity={80} style={styles.blurBackgroundRound} tint="dark" />
) : (
Constants.executionEnvironment === ExecutionEnvironment.StoreClient ? (
<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)"
/>
)
<View style={styles.androidFallbackBlurRound} />
)}
<MaterialIcons
name="assessment"

View file

@ -157,6 +157,8 @@ const AndroidVideoPlayer: React.FC = () => {
const isMounted = useRef(true);
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
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 = () => {
Animated.timing(fadeAnim, {
@ -355,7 +357,9 @@ const AndroidVideoPlayer: React.FC = () => {
};
}, [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 (DEBUG_MODE) {
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) => {
progressBarRef.current?.measure((x, y, width, height, pageX, pageY) => {
const percentage = Math.max(0, Math.min(locationX / width, 1));
const seekTime = percentage * duration;
const percentage = Math.max(0, Math.min(locationX / width, 0.999));
const seekTime = Math.min(percentage * duration, duration - END_EPSILON);
progressAnim.setValue(percentage);
if (isDragging) {
pendingSeekValue.current = seekTime;
@ -519,7 +523,7 @@ const AndroidVideoPlayer: React.FC = () => {
const skip = (seconds: number) => {
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);
}
};

View file

@ -152,6 +152,9 @@ const VideoPlayer: React.FC = () => {
const isMounted = useRef(true);
const controlsTimeout = useRef<NodeJS.Timeout | null>(null);
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 = () => {
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 (DEBUG_MODE) {
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) => {
progressBarRef.current?.measure((x, y, width, height, pageX, pageY) => {
const percentage = Math.max(0, Math.min(locationX / width, 1));
const seekTime = percentage * duration;
const percentage = Math.max(0, Math.min(locationX / width, 0.999));
const seekTime = Math.min(percentage * duration, duration - END_EPSILON);
progressAnim.setValue(percentage);
if (isDragging) {
pendingSeekValue.current = seekTime;
@ -530,7 +535,7 @@ const VideoPlayer: React.FC = () => {
const skip = (seconds: number) => {
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);
}
};

View file

@ -462,70 +462,6 @@ const createStyles = (colors: any) => StyleSheet.create({
padding: 6,
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: {
height: 1,
backgroundColor: colors.border,
@ -560,7 +496,7 @@ const createStyles = (colors: any) => StyleSheet.create({
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.4)',
backgroundColor: 'black',
},
androidBlurContainer: {
position: 'absolute',
@ -618,14 +554,8 @@ const AddonsScreen = () => {
const colors = currentTheme.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(() => {
loadAddons();
loadCommunityAddons();
}, []);
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 urlToInstall = url || addonUrl;
if (!urlToInstall) {
Alert.alert('Error', 'Please enter an addon URL or select a community addon');
Alert.alert('Error', 'Please enter an addon URL');
return;
}
@ -728,7 +634,6 @@ const AddonsScreen = () => {
const refreshAddons = async () => {
loadAddons();
loadCommunityAddons();
};
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 }) => (
<View style={styles.statsCard}>
<Text style={styles.statsValue}>{value}</Text>
@ -1186,95 +1031,6 @@ const AddonsScreen = () => {
)}
</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>
)}

View file

@ -29,7 +29,6 @@ import { useTheme } from '../contexts/ThemeContext';
import { catalogService } from '../services/catalogService';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import * as Sentry from '@sentry/react-native';
import Animated, { FadeInDown } from 'react-native-reanimated';
const { width } = Dimensions.get('window');
@ -39,15 +38,13 @@ const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
interface SettingsCardProps {
children: React.ReactNode;
title?: string;
delay?: number;
}
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title, delay = 0 }) => {
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title }) => {
const { currentTheme } = useTheme();
return (
<Animated.View
entering={FadeInDown.delay(delay).duration(400)}
<View
style={[styles.cardContainer]}
>
{title && (
@ -64,7 +61,7 @@ const SettingsCard: React.FC<SettingsCardProps> = ({ children, title, delay = 0
]}>
{children}
</View>
</Animated.View>
</View>
);
};
@ -297,7 +294,7 @@ const SettingsScreen: React.FC = () => {
contentContainerStyle={styles.scrollContent}
>
{/* Account Section */}
<SettingsCard title="ACCOUNT" delay={0}>
<SettingsCard title="ACCOUNT">
<SettingItem
title="Trakt"
description={isAuthenticated ? `@${userProfile?.username || 'User'}` : "Sign in to sync"}
@ -318,7 +315,7 @@ const SettingsScreen: React.FC = () => {
</SettingsCard>
{/* Content & Discovery */}
<SettingsCard title="CONTENT & DISCOVERY" delay={100}>
<SettingsCard title="CONTENT & DISCOVERY">
<SettingItem
title="Addons"
description={`${addonCount} installed`}
@ -344,7 +341,7 @@ const SettingsScreen: React.FC = () => {
</SettingsCard>
{/* Appearance & Interface */}
<SettingsCard title="APPEARANCE" delay={200}>
<SettingsCard title="APPEARANCE">
<SettingItem
title="Theme"
description={currentTheme.name}
@ -367,7 +364,7 @@ const SettingsScreen: React.FC = () => {
</SettingsCard>
{/* Integrations */}
<SettingsCard title="INTEGRATIONS" delay={300}>
<SettingsCard title="INTEGRATIONS">
<SettingItem
title="MDBList"
description={mdblistKeySet ? "Connected" : "Enable to add ratings & reviews"}
@ -393,7 +390,7 @@ const SettingsScreen: React.FC = () => {
</SettingsCard>
{/* Playback & Experience */}
<SettingsCard title="PLAYBACK" delay={400}>
<SettingsCard title="PLAYBACK">
<SettingItem
title="Video Player"
description={Platform.OS === 'ios'
@ -422,7 +419,7 @@ const SettingsScreen: React.FC = () => {
</SettingsCard>
{/* About & Support */}
<SettingsCard title="ABOUT" delay={500}>
<SettingsCard title="ABOUT">
<SettingItem
title="Privacy Policy"
icon="lock"
@ -445,7 +442,7 @@ const SettingsScreen: React.FC = () => {
{/* Developer Options - Only show in development */}
{__DEV__ && (
<SettingsCard title="DEVELOPER" delay={600}>
<SettingsCard title="DEVELOPER">
<SettingItem
title="Test Onboarding"
icon="play-circle-outline"
@ -496,7 +493,7 @@ const SettingsScreen: React.FC = () => {
{/* Cache Management - Only show if MDBList is connected */}
{mdblistKeySet && (
<SettingsCard title="CACHE MANAGEMENT" delay={600}>
<SettingsCard title="CACHE MANAGEMENT">
<SettingItem
title="Clear MDBList Cache"
icon="cached"