ui updates trakt

This commit is contained in:
tapframe 2025-10-19 15:37:30 +05:30
parent 32bec08f30
commit eb3082cddb
10 changed files with 38 additions and 125 deletions

16
package-lock.json generated
View file

@ -84,8 +84,7 @@
"react-native-video": "^6.17.0",
"react-native-web": "^0.21.0",
"react-native-wheel-color-picker": "^1.3.1",
"react-native-worklets": "^0.6.1",
"toastify-react-native": "^7.2.3"
"react-native-worklets": "^0.6.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
@ -12854,19 +12853,6 @@
"node": ">=8.0"
}
},
"node_modules/toastify-react-native": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/toastify-react-native/-/toastify-react-native-7.2.3.tgz",
"integrity": "sha512-ngmpTKlTo0IRddwSsNWK+YKbB2veqotHy7Zpil4eksoLAlq0RPSgdVOk5QDEDUONJQ4r7ljGYeRW68KBztirsg==",
"license": "MIT",
"dependencies": {
"react-native-vector-icons": "*"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View file

@ -84,8 +84,7 @@
"react-native-video": "^6.17.0",
"react-native-web": "^0.21.0",
"react-native-wheel-color-picker": "^1.3.1",
"react-native-worklets": "^0.6.1",
"toastify-react-native": "^7.2.3"
"react-native-worklets": "^0.6.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",

View file

@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import { Platform } from 'react-native';
import { Toast } from 'toastify-react-native';
import { toastService } from '../services/toastService';
import UpdateService, { UpdateInfo } from '../services/updateService';
import AsyncStorage from '@react-native-async-storage/async-storage';
@ -78,13 +78,13 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
// The app will automatically reload with the new version
console.log('Update installed successfully');
} else {
Toast.error('Unable to install the update. Please try again later or check your internet connection.');
toastService.showError('Installation Failed', 'Unable to install the update. Please try again later or check your internet connection.');
// Show popup again after failed installation
setShowUpdatePopup(true);
}
} catch (error) {
if (__DEV__) console.error('Error installing update:', error);
Toast.error('An error occurred while installing the update. Please try again later.');
toastService.showError('Installation Error', 'An error occurred while installing the update. Please try again later.');
// Show popup again after error
setShowUpdatePopup(true);
} finally {
@ -135,7 +135,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
(async () => {
try { await AsyncStorage.setItem(UPDATE_BADGE_KEY, 'true'); } catch {}
})();
try { Toast.info('Update available — go to Settings → App Updates'); } catch {}
toastService.showInfo('Update Available', 'Update available — go to Settings → App Updates');
setShowUpdatePopup(false);
} else {
setShowUpdatePopup(true);

View file

@ -15,7 +15,6 @@ import { HeaderVisibility } from '../contexts/HeaderVisibility';
import { Stream } from '../types/streams';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from '../contexts/ThemeContext';
import ToastManager from 'toastify-react-native';
import { PostHogProvider } from 'posthog-react-native';
// Optional iOS Glass effect (expo-glass-effect) with safe fallback
@ -1499,85 +1498,6 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
</Stack.Navigator>
</View>
</PaperProvider>
{/* Global toast customization using ThemeContext */}
<ToastManager
position="top"
useModal={false}
theme={'dark'}
// Dimensions
width={'90%'}
minHeight={61}
// Icon defaults
iconFamily="MaterialIcons"
iconSize={22}
icons={{
success: 'check-circle',
error: 'error',
info: 'info',
warn: 'warning',
default: 'notifications',
}}
// Close icon defaults
showCloseIcon={true}
closeIcon={'close'}
closeIconFamily={'MaterialIcons'}
closeIconSize={18}
// Spacing (ensure below safe area)
topOffset={Math.max(8, insets.top + 8)}
bottomOffset={40}
// Styles bound to ThemeContext
style={{
backgroundColor: currentTheme.colors.darkBackground,
borderRadius: 12,
paddingVertical: 12,
paddingHorizontal: 14,
}}
textStyle={{
color: currentTheme.colors.highEmphasis,
fontWeight: '600',
}}
config={{
default: (props: any) => (
<View style={{
backgroundColor: currentTheme.colors.elevation2,
borderRadius: 12,
padding: 12,
width: '100%'
}}>
<Text style={{ color: currentTheme.colors.highEmphasis, fontWeight: '700' }}>{props.text1}</Text>
{props.text2 ? (
<Text style={{ color: currentTheme.colors.mediumEmphasis, marginTop: 4 }}>{props.text2}</Text>
) : null}
</View>
),
success: (props: any) => (
<View style={{
backgroundColor: currentTheme.colors.elevation2,
borderRadius: 12,
padding: 12,
width: '100%'
}}>
<Text style={{ color: currentTheme.colors.success || '#4CAF50', fontWeight: '800' }}>{props.text1}</Text>
{props.text2 ? (
<Text style={{ color: currentTheme.colors.mediumEmphasis, marginTop: 4 }}>{props.text2}</Text>
) : null}
</View>
),
error: (props: any) => (
<View style={{
backgroundColor: currentTheme.colors.elevation2,
borderRadius: 12,
padding: 12,
width: '100%'
}}>
<Text style={{ color: currentTheme.colors.error || '#ff4444', fontWeight: '800' }}>{props.text1}</Text>
{props.text2 ? (
<Text style={{ color: currentTheme.colors.mediumEmphasis, marginTop: 4 }}>{props.text2}</Text>
) : null}
</View>
),
}}
/>
</SafeAreaProvider>
);
};

View file

@ -7,7 +7,7 @@ import { useTheme } from '../contexts/ThemeContext';
import { useAccount } from '../contexts/AccountContext';
import { useNavigation, useRoute } from '@react-navigation/native';
import * as Haptics from 'expo-haptics';
import ToastManager, { Toast } from 'toastify-react-native';
import { useToast } from '../contexts/ToastContext';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const { width, height } = Dimensions.get('window');
@ -19,6 +19,7 @@ const AuthScreen: React.FC = () => {
const route = useRoute<any>();
const fromOnboarding = !!route?.params?.fromOnboarding;
const insets = useSafeAreaInsets();
const { showError, showSuccess } = useToast();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
@ -149,7 +150,7 @@ const AuthScreen: React.FC = () => {
if (mode === 'signup' && signupDisabled) {
const msg = 'Sign up is currently disabled due to upcoming system changes';
setError(msg);
Toast.error(msg);
showError('Sign Up Disabled', 'Sign up is currently disabled due to upcoming system changes');
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
return;
}
@ -157,21 +158,21 @@ const AuthScreen: React.FC = () => {
if (!isEmailValid) {
const msg = 'Enter a valid email address';
setError(msg);
Toast.error(msg);
showError('Invalid Email', 'Enter a valid email address');
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
return;
}
if (!isPasswordValid) {
const msg = 'Password must be at least 6 characters';
setError(msg);
Toast.error(msg);
showError('Password Too Short', 'Password must be at least 6 characters');
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
return;
}
if (mode === 'signup' && !passwordsMatch) {
const msg = 'Passwords do not match';
setError(msg);
Toast.error(msg);
showError('Passwords Don\'t Match', 'Passwords do not match');
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
return;
}
@ -180,11 +181,11 @@ const AuthScreen: React.FC = () => {
const err = mode === 'signin' ? await signIn(email.trim(), password) : await signUp(email.trim(), password);
if (err) {
setError(err);
Toast.error(err);
showError('Authentication Failed', err);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
} else {
const msg = mode === 'signin' ? 'Logged in successfully' : 'Sign up successful';
Toast.success(msg);
showSuccess('Success', msg);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
// Navigate to main tabs after successful authentication

View file

@ -29,7 +29,7 @@ import { LinearGradient } from 'expo-linear-gradient';
import FastImage from '@d11/react-native-fast-image';
import { useDownloads } from '../contexts/DownloadsContext';
import type { DownloadItem } from '../contexts/DownloadsContext';
import { Toast } from 'toastify-react-native';
import { useToast } from '../contexts/ToastContext';
import CustomAlert from '../components/CustomAlert';
const { height, width } = Dimensions.get('window');
@ -98,6 +98,7 @@ const DownloadItemComponent: React.FC<{
onRequestRemove: (item: DownloadItem) => void;
}> = React.memo(({ item, onPress, onAction, onRequestRemove }) => {
const { currentTheme } = useTheme();
const { showSuccess, showInfo } = useToast();
const [posterUrl, setPosterUrl] = useState<string | null>(item.posterUrl || null);
// Try to fetch poster if not available
@ -113,18 +114,18 @@ const DownloadItemComponent: React.FC<{
if (item.status === 'completed' && item.fileUri) {
Clipboard.setString(item.fileUri);
if (Platform.OS === 'android') {
Toast.success('Local file path copied to clipboard');
showSuccess('Path Copied', 'Local file path copied to clipboard');
} else {
Alert.alert('Copied', 'Local file path copied to clipboard');
}
} else if (item.status !== 'completed') {
if (Platform.OS === 'android') {
Toast.info('Download is not complete yet');
showInfo('Download Incomplete', 'Download is not complete yet');
} else {
Alert.alert('Not Available', 'The local file path is available only after the download is complete.');
}
}
}, [item.status, item.fileUri]);
}, [item.status, item.fileUri, showSuccess, showInfo]);
const formatBytes = (bytes?: number) => {
if (!bytes || bytes <= 0) return '0 B';
@ -343,6 +344,7 @@ const DownloadsScreen: React.FC = () => {
const { currentTheme } = useTheme();
const { top: safeAreaTop } = useSafeAreaInsets();
const { downloads, pauseDownload, resumeDownload, cancelDownload } = useDownloads();
const { showSuccess, showInfo } = useToast();
const [isRefreshing, setIsRefreshing] = useState(false);
const [selectedFilter, setSelectedFilter] = useState<'all' | 'downloading' | 'completed' | 'paused'>('all');

View file

@ -59,7 +59,7 @@ import { useLoading } from '../contexts/LoadingContext';
import * as ScreenOrientation from 'expo-screen-orientation';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Toast } from 'toastify-react-native';
import { useToast } from '../contexts/ToastContext';
import FirstTimeWelcome from '../components/FirstTimeWelcome';
import { HeaderVisibility } from '../contexts/HeaderVisibility';
@ -111,6 +111,7 @@ const HomeScreen = () => {
const continueWatchingRef = useRef<ContinueWatchingRef>(null);
const { settings } = useSettings();
const { lastUpdate } = useCatalogContext(); // Add catalog context to listen for addon changes
const { showInfo } = useToast();
const [showHeroSection, setShowHeroSection] = useState(settings.showHeroSection);
const [featuredContentSource, setFeaturedContentSource] = useState(settings.featuredContentSource);
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@ -351,7 +352,7 @@ const HomeScreen = () => {
await AsyncStorage.removeItem('showLoginHintToastOnce');
hideTimer = setTimeout(() => setHintVisible(false), 2000);
// Also show a global toast for consistency across screens
try { Toast.info('You can sign in anytime from Settings → Account', 'bottom'); } catch {}
showInfo('Sign In Available', 'You can sign in anytime from Settings → Account');
}
} catch {}
})();

View file

@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { DeviceEventEmitter } from 'react-native';
import { Share } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Toast } from 'toastify-react-native';
import { useToast } from '../contexts/ToastContext';
import DropUpMenu from '../components/home/DropUpMenu';
import {
View,
@ -208,6 +208,7 @@ const LibraryScreen = () => {
const [filter, setFilter] = useState<'trakt' | 'movies' | 'series'>('movies');
const [showTraktContent, setShowTraktContent] = useState(false);
const [selectedTraktFolder, setSelectedTraktFolder] = useState<string | null>(null);
const { showInfo, showError } = useToast();
// DropUpMenu state
const [menuVisible, setMenuVisible] = useState(false);
const [selectedItem, setSelectedItem] = useState<LibraryItem | null>(null);
@ -1005,11 +1006,11 @@ const LibraryScreen = () => {
case 'library': {
try {
await catalogService.removeFromLibrary(selectedItem.type, selectedItem.id);
Toast.info('Removed from Library');
showInfo('Removed from Library', 'Item removed from your library');
setLibraryItems(prev => prev.filter(item => !(item.id === selectedItem.id && item.type === selectedItem.type)));
setMenuVisible(false);
} catch (error) {
Toast.error('Failed to update Library');
showError('Failed to update Library', 'Unable to remove item from library');
}
break;
}
@ -1019,7 +1020,7 @@ const LibraryScreen = () => {
const key = `watched:${selectedItem.type}:${selectedItem.id}`;
const newWatched = !selectedItem.watched;
await AsyncStorage.setItem(key, newWatched ? 'true' : 'false');
Toast.info(newWatched ? 'Marked as Watched' : 'Marked as Unwatched');
showInfo(newWatched ? 'Marked as Watched' : 'Marked as Unwatched', newWatched ? 'Item marked as watched' : 'Item marked as unwatched');
// Instantly update local state
setLibraryItems(prev => prev.map(item =>
item.id === selectedItem.id && item.type === selectedItem.type
@ -1027,7 +1028,7 @@ const LibraryScreen = () => {
: item
));
} catch (error) {
Toast.error('Failed to update watched status');
showError('Failed to update watched status', 'Unable to update watched status');
}
break;
}

View file

@ -47,7 +47,7 @@ import QualityBadge from '../components/metadata/QualityBadge';
import { logger } from '../utils/logger';
import { isMkvStream } from '../utils/mkvDetection';
import CustomAlert from '../components/CustomAlert';
import { Toast } from 'toastify-react-native';
import { useToast } from '../contexts/ToastContext';
import { useDownloads } from '../contexts/DownloadsContext';
import { PaperProvider } from 'react-native-paper';
@ -227,6 +227,7 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
const { useSettings } = require('../hooks/useSettings');
const { settings } = useSettings();
const { startDownload } = useDownloads();
const { showSuccess, showInfo } = useToast();
// Handle long press to copy stream URL to clipboard
const handleLongPress = useCallback(async () => {
@ -236,7 +237,7 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
// Use toast for Android, custom alert for iOS
if (Platform.OS === 'android') {
Toast.success('Stream URL copied to clipboard!', 'bottom');
showSuccess('URL Copied', 'Stream URL copied to clipboard!');
} else {
// iOS uses custom alert
showAlert('Copied!', 'Stream URL has been copied to clipboard.');
@ -244,13 +245,13 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
} catch (error) {
// Fallback: show URL in alert if clipboard fails
if (Platform.OS === 'android') {
Toast.info(`Stream URL: ${stream.url}`, 'bottom');
showInfo('Stream URL', `Stream URL: ${stream.url}`);
} else {
showAlert('Stream URL', stream.url);
}
}
}
}, [stream.url, showAlert]);
}, [stream.url, showAlert, showSuccess, showInfo]);
const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]);
const streamInfo = useMemo(() => {
@ -513,6 +514,7 @@ export const StreamsScreen = () => {
const { currentTheme } = useTheme();
const { colors } = currentTheme;
const { pauseTrailer, resumeTrailer } = useTrailer();
const { showSuccess, showInfo } = useToast();
// Add refs to prevent excessive updates and duplicate loads
const isMounted = useRef(true);

View file

@ -11,7 +11,7 @@ import {
Dimensions,
Linking
} from 'react-native';
import { Toast } from 'toastify-react-native';
import { useToast } from '../contexts/ToastContext';
import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons';
@ -70,6 +70,7 @@ const UpdateScreen: React.FC = () => {
const { currentTheme } = useTheme();
const insets = useSafeAreaInsets();
const github = useGithubMajorUpdate();
const { showInfo } = useToast();
// CustomAlert state
const [alertVisible, setAlertVisible] = useState(false);
@ -152,7 +153,7 @@ const UpdateScreen: React.FC = () => {
// Also refresh GitHub section on mount (works in dev and prod)
try { github.refresh(); } catch {}
if (Platform.OS === 'android') {
try { Toast.info('Checking for updates…'); } catch {}
showInfo('Checking for Updates', 'Checking for updates…');
}
}, []);