Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 1 addition & 59 deletions frontend/src/components/common/AppContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,62 +161,4 @@ export default function AppContent() {
);

Notifications.getLastNotificationResponseAsync().then(response => {
if (response) handleNotificationResponse(response);
});

return () => { responseListener.remove(); };
}, [user_token, isGuest]);

useEffect(() => {
firebaseInit();
cleanUpDownloads();
}, []);

return (
<SafeAreaProvider>
<TamaguiProvider
config={config}
defaultTheme={isDarkMode ? 'dark' : 'light'}>
<PaperProvider>
<FirebaseProvider>
<PreferencesProvider>
<SocketProvider>
<View style={{ flex: 1 }}>
<NetworkBanner />
<AppInner
navigationRef={navigationRef}
visible={visible}
storeUrl={storeUrl}
/>
</View>
</SocketProvider>
</PreferencesProvider>
</FirebaseProvider>
</PaperProvider>
</TamaguiProvider>
</SafeAreaProvider>
);
}

function AppInner({
navigationRef,
visible,
storeUrl,
}: {
navigationRef: React.RefObject<NavigationContainerRef<any> | null>;
visible: boolean;
storeUrl: string;
}) {
const theme = useTheme();
const isDarkMode = useColorScheme() === 'dark';

return (
<View style={{ flex: 1, backgroundColor: theme?.background?.get() ?? (isDarkMode ? '#121212' : '#ffffff') }}>
<NavigationContainer ref={navigationRef}>
<StackNavigation />
</NavigationContainer>
<CustomAlertDialog key={'alert'} />
<UpdateModal visible={visible} storeUrl={storeUrl} />
</View>
);
}
.catch(err => console.error(err))
205 changes: 1 addition & 204 deletions frontend/src/components/podcast/PodcastCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,207 +57,4 @@ const PodcastCard = ({
React.useCallback(() => {
let isMounted = true;
getPlaybackPosition(id).then(pos => {
if (isMounted) {
setProgress(pos);
}
});
return () => {
isMounted = false;
};
}, [id])
);

const handleOpenSheet = () => {
if (isGuest) {
navigation.navigate('GuestPlaceholderScreen', {
title: 'Sign In Required',
description: 'Please sign in or sign up for more actions.',
iconName: 'ellipsis-v',
});
return;
}
sheetRef.current?.present();
};

const handleShare = async () => {
try {
const url = `https://uhsocial.in/api/share/podcast?trackId=${id}&audioUrl=${audioUrl}`;
const result = await Share.open({
title: title,
message: `${title} : Check out this awesome podcast on UltimateHealth app!`,
url: url,
subject: 'Podcast Sharing',
});
console.log(result);
} catch (error) {
console.log('Error sharing:', error);
Alert.alert('Error', 'Something went wrong while sharing.');
}
};

const uri =
imageUri && imageUri !== ''
? imageUri.startsWith('https')
? imageUri
: `${GET_STORAGE_DATA}/${imageUri}`
: 'https://t3.ftcdn.net/jpg/05/10/75/30/360_F_510753092_f4AOmCJAczuGgRLCmHxmowga2tC9VYQP.jpg';

return (
<TouchableOpacity onPress={handleClick} activeOpacity={0.9} style={styles.cardWrapper}>
<View style={[GlassStyles.glassCardElevated, styles.cardContainer]}>
<View style={styles.imageContainer}>
<Image
source={{uri}}
style={styles.coverImage}
borderRadius={BorderRadius.lg}
/>
<View style={styles.imageOverlay}>
<View style={[styles.playButton, GlassStyles.glassContainer]}>
<Ionicons name="play" size={24} color={ProfessionalColors.white} />
</View>
</View>
{progress && (
<View style={styles.progressBarContainer}>
<View style={[styles.progressBar, { width: `${progress.duration > 0 ? Math.min(progress.position / progress.duration, 1) * 100 : 0}%` }]} />
</View>
)}
</View>

<YStack padding="$3" gap="$2" flex={1}>
<Text
fontSize={17}
fontWeight="700"
color={ProfessionalColors.gray900}
numberOfLines={2}
ellipsizeMode="tail">
{title}
</Text>

<XStack alignItems="center" gap="$2">
<Ionicons name="person-circle-outline" size={18} color={ProfessionalColors.gray600} />
<Text fontSize={14} fontWeight="500" color={ProfessionalColors.gray700}>
{host}
</Text>
</XStack>

<XStack flexWrap="wrap" gap="$1.5" marginTop="$1">
{tags?.slice(0, 3).map((tag, i) => (
<View key={i} style={styles.tag}>
<Text style={styles.tagText}>#{tag.name}</Text>
</View>
))}
</XStack>

<XStack alignItems="center" justifyContent="space-between" marginTop="$2">
<XStack alignItems="center" gap="$1">
<Ionicons name="eye-outline" size={16} color={ProfessionalColors.gray600} />
<Text fontSize={13} fontWeight="500" color={ProfessionalColors.gray600}>
{views <= 1 ? `${views} view` : `${formatCount(views)} views`}
</Text>
</XStack>

<XStack alignItems="center" gap="$1">
<Ionicons name="time-outline" size={16} color={ProfessionalColors.gray600} />
<Text fontSize={13} fontWeight="500" color={ProfessionalColors.gray600}>
{duration}
</Text>
</XStack>
</XStack>
</YStack>

{display && !isGuest && (
<TouchableOpacity
style={[styles.menuButton, GlassStyles.glassContainer]}
onPress={handleOpenSheet}
activeOpacity={0.7}>
<Entypo name="dots-three-vertical" size={18} color={ProfessionalColors.gray800} />
</TouchableOpacity>
)}

<PodcastActions
ref={sheetRef}
downloaded={downloaded}
onShare={handleShare}
onReport={handleReport}
onDownload={downLoadAudio}
onSave={() => playlistAct(id)}
/>
</View>
</TouchableOpacity>
);
};

const styles = StyleSheet.create({
cardWrapper: {
marginVertical: 8,
width: '100%',
},
cardContainer: {
overflow: 'hidden',
position: 'relative',
},
imageContainer: {
width: '100%',
height: PODCAST_CARD.imageHeight,
position: 'relative',
overflow: 'hidden',
},
coverImage: {
width: '100%',
height: '100%',
resizeMode: 'cover',
},
imageOverlay: {
...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
justifyContent: 'center',
alignItems: 'center',
},
playButton: {
width: 56,
height: 56,
borderRadius: 28,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 191, 255, 0.9)',
},
tag: {
backgroundColor: ProfessionalColors.primaryGlass,
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: BorderRadius.lg,
borderWidth: 1,
borderColor: ProfessionalColors.primary + '30',
},
tagText: {
fontSize: 11,
fontWeight: '600',
color: ProfessionalColors.primary,
textTransform: 'capitalize',
},
menuButton: {
position: 'absolute',
top: 12,
right: 12,
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
zIndex: 10,
},
progressBarContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 4,
backgroundColor: 'rgba(255, 255, 255, 0.3)',
},
progressBar: {
height: '100%',
backgroundColor: ProfessionalColors.primary,
},
});

export default PodcastCard;
.catch(err => console.error(err))
70 changes: 1 addition & 69 deletions frontend/src/contexts/PreferencesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,72 +128,4 @@ export const PreferencesProvider: React.FC<PreferencesProviderProps> = ({
return resolved;
});
const write = pendingWriteRef.current.then(() =>
savePreferencesToStorage(resolved)
);
pendingWriteRef.current = write;
await write;
},
[savePreferencesToStorage]
);

// ✅ FIXED: Derives the next array from the latest committed state via
// a functional updater, not from `preferredLanguages` captured in this
// callback's closure, so concurrent toggles no longer clobber each other.
const addLanguagePreference = useCallback(
async (language: LanguageCode): Promise<void> => {
if (!isValidLanguageCode(language)) {
console.warn(
`[PreferencesContext] Invalid language code: ${language}`
);
return;
}
await setPreferredLanguages(prev =>
prev.includes(language) ? prev : [...prev, language]
);
},
[setPreferredLanguages]
);

// ✅ FIXED: Derives the next array from the latest committed state via
// a functional updater, not from `preferredLanguages` captured in this
// callback's closure, so concurrent toggles no longer clobber each other.
const removeLanguagePreference = useCallback(
async (language: LanguageCode): Promise<void> => {
await setPreferredLanguages(prev =>
prev.includes(language)
? prev.filter(lang => lang !== language)
: prev
);
},
[setPreferredLanguages]
);

const value: PreferencesContextType = {
preferredLanguages,
setPreferredLanguages,
addLanguagePreference,
removeLanguagePreference,
isLoading,
hasLanguagePreferences: preferredLanguages.length > 0,
};

return (
<PreferencesContext.Provider value={value}>
{children}
</PreferencesContext.Provider>
);
};

/**
* Hook to use PreferencesContext
* Must be called within PreferencesProvider
*/
export const usePreferences = (): PreferencesContextType => {
const context = useContext(PreferencesContext);
if (context === undefined) {
throw new Error(
'usePreferences must be used within a PreferencesProvider'
);
}
return context;
};
.catch(err => console.error(err))
Loading