mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
ui updates trakt
This commit is contained in:
parent
32bec08f30
commit
eb3082cddb
10 changed files with 38 additions and 125 deletions
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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…');
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue