mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-03 16:59:08 +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-video": "^6.17.0",
|
||||||
"react-native-web": "^0.21.0",
|
"react-native-web": "^0.21.0",
|
||||||
"react-native-wheel-color-picker": "^1.3.1",
|
"react-native-wheel-color-picker": "^1.3.1",
|
||||||
"react-native-worklets": "^0.6.1",
|
"react-native-worklets": "^0.6.1"
|
||||||
"toastify-react-native": "^7.2.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|
@ -12854,19 +12853,6 @@
|
||||||
"node": ">=8.0"
|
"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": {
|
"node_modules/toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,7 @@
|
||||||
"react-native-video": "^6.17.0",
|
"react-native-video": "^6.17.0",
|
||||||
"react-native-web": "^0.21.0",
|
"react-native-web": "^0.21.0",
|
||||||
"react-native-wheel-color-picker": "^1.3.1",
|
"react-native-wheel-color-picker": "^1.3.1",
|
||||||
"react-native-worklets": "^0.6.1",
|
"react-native-worklets": "^0.6.1"
|
||||||
"toastify-react-native": "^7.2.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
import { Toast } from 'toastify-react-native';
|
import { toastService } from '../services/toastService';
|
||||||
import UpdateService, { UpdateInfo } from '../services/updateService';
|
import UpdateService, { UpdateInfo } from '../services/updateService';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
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
|
// The app will automatically reload with the new version
|
||||||
console.log('Update installed successfully');
|
console.log('Update installed successfully');
|
||||||
} else {
|
} 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
|
// Show popup again after failed installation
|
||||||
setShowUpdatePopup(true);
|
setShowUpdatePopup(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (__DEV__) console.error('Error installing update:', 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
|
// Show popup again after error
|
||||||
setShowUpdatePopup(true);
|
setShowUpdatePopup(true);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -135,7 +135,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try { await AsyncStorage.setItem(UPDATE_BADGE_KEY, 'true'); } catch {}
|
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);
|
setShowUpdatePopup(false);
|
||||||
} else {
|
} else {
|
||||||
setShowUpdatePopup(true);
|
setShowUpdatePopup(true);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
||||||
import { Stream } from '../types/streams';
|
import { Stream } from '../types/streams';
|
||||||
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { useTheme } from '../contexts/ThemeContext';
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
import ToastManager from 'toastify-react-native';
|
|
||||||
import { PostHogProvider } from 'posthog-react-native';
|
import { PostHogProvider } from 'posthog-react-native';
|
||||||
|
|
||||||
// Optional iOS Glass effect (expo-glass-effect) with safe fallback
|
// Optional iOS Glass effect (expo-glass-effect) with safe fallback
|
||||||
|
|
@ -1499,85 +1498,6 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</View>
|
</View>
|
||||||
</PaperProvider>
|
</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>
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { useTheme } from '../contexts/ThemeContext';
|
||||||
import { useAccount } from '../contexts/AccountContext';
|
import { useAccount } from '../contexts/AccountContext';
|
||||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||||
import * as Haptics from 'expo-haptics';
|
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';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
@ -19,6 +19,7 @@ const AuthScreen: React.FC = () => {
|
||||||
const route = useRoute<any>();
|
const route = useRoute<any>();
|
||||||
const fromOnboarding = !!route?.params?.fromOnboarding;
|
const fromOnboarding = !!route?.params?.fromOnboarding;
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
const { showError, showSuccess } = useToast();
|
||||||
|
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
|
|
@ -149,7 +150,7 @@ const AuthScreen: React.FC = () => {
|
||||||
if (mode === 'signup' && signupDisabled) {
|
if (mode === 'signup' && signupDisabled) {
|
||||||
const msg = 'Sign up is currently disabled due to upcoming system changes';
|
const msg = 'Sign up is currently disabled due to upcoming system changes';
|
||||||
setError(msg);
|
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(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -157,21 +158,21 @@ const AuthScreen: React.FC = () => {
|
||||||
if (!isEmailValid) {
|
if (!isEmailValid) {
|
||||||
const msg = 'Enter a valid email address';
|
const msg = 'Enter a valid email address';
|
||||||
setError(msg);
|
setError(msg);
|
||||||
Toast.error(msg);
|
showError('Invalid Email', 'Enter a valid email address');
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isPasswordValid) {
|
if (!isPasswordValid) {
|
||||||
const msg = 'Password must be at least 6 characters';
|
const msg = 'Password must be at least 6 characters';
|
||||||
setError(msg);
|
setError(msg);
|
||||||
Toast.error(msg);
|
showError('Password Too Short', 'Password must be at least 6 characters');
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mode === 'signup' && !passwordsMatch) {
|
if (mode === 'signup' && !passwordsMatch) {
|
||||||
const msg = 'Passwords do not match';
|
const msg = 'Passwords do not match';
|
||||||
setError(msg);
|
setError(msg);
|
||||||
Toast.error(msg);
|
showError('Passwords Don\'t Match', 'Passwords do not match');
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -180,11 +181,11 @@ const AuthScreen: React.FC = () => {
|
||||||
const err = mode === 'signin' ? await signIn(email.trim(), password) : await signUp(email.trim(), password);
|
const err = mode === 'signin' ? await signIn(email.trim(), password) : await signUp(email.trim(), password);
|
||||||
if (err) {
|
if (err) {
|
||||||
setError(err);
|
setError(err);
|
||||||
Toast.error(err);
|
showError('Authentication Failed', err);
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
const msg = mode === 'signin' ? 'Logged in successfully' : 'Sign up successful';
|
const msg = mode === 'signin' ? 'Logged in successfully' : 'Sign up successful';
|
||||||
Toast.success(msg);
|
showSuccess('Success', msg);
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||||
|
|
||||||
// Navigate to main tabs after successful authentication
|
// 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 FastImage from '@d11/react-native-fast-image';
|
||||||
import { useDownloads } from '../contexts/DownloadsContext';
|
import { useDownloads } from '../contexts/DownloadsContext';
|
||||||
import type { DownloadItem } 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';
|
import CustomAlert from '../components/CustomAlert';
|
||||||
|
|
||||||
const { height, width } = Dimensions.get('window');
|
const { height, width } = Dimensions.get('window');
|
||||||
|
|
@ -98,6 +98,7 @@ const DownloadItemComponent: React.FC<{
|
||||||
onRequestRemove: (item: DownloadItem) => void;
|
onRequestRemove: (item: DownloadItem) => void;
|
||||||
}> = React.memo(({ item, onPress, onAction, onRequestRemove }) => {
|
}> = React.memo(({ item, onPress, onAction, onRequestRemove }) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
const { showSuccess, showInfo } = useToast();
|
||||||
const [posterUrl, setPosterUrl] = useState<string | null>(item.posterUrl || null);
|
const [posterUrl, setPosterUrl] = useState<string | null>(item.posterUrl || null);
|
||||||
|
|
||||||
// Try to fetch poster if not available
|
// Try to fetch poster if not available
|
||||||
|
|
@ -113,18 +114,18 @@ const DownloadItemComponent: React.FC<{
|
||||||
if (item.status === 'completed' && item.fileUri) {
|
if (item.status === 'completed' && item.fileUri) {
|
||||||
Clipboard.setString(item.fileUri);
|
Clipboard.setString(item.fileUri);
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
Toast.success('Local file path copied to clipboard');
|
showSuccess('Path Copied', 'Local file path copied to clipboard');
|
||||||
} else {
|
} else {
|
||||||
Alert.alert('Copied', 'Local file path copied to clipboard');
|
Alert.alert('Copied', 'Local file path copied to clipboard');
|
||||||
}
|
}
|
||||||
} else if (item.status !== 'completed') {
|
} else if (item.status !== 'completed') {
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
Toast.info('Download is not complete yet');
|
showInfo('Download Incomplete', 'Download is not complete yet');
|
||||||
} else {
|
} else {
|
||||||
Alert.alert('Not Available', 'The local file path is available only after the download is complete.');
|
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) => {
|
const formatBytes = (bytes?: number) => {
|
||||||
if (!bytes || bytes <= 0) return '0 B';
|
if (!bytes || bytes <= 0) return '0 B';
|
||||||
|
|
@ -343,6 +344,7 @@ const DownloadsScreen: React.FC = () => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const { top: safeAreaTop } = useSafeAreaInsets();
|
const { top: safeAreaTop } = useSafeAreaInsets();
|
||||||
const { downloads, pauseDownload, resumeDownload, cancelDownload } = useDownloads();
|
const { downloads, pauseDownload, resumeDownload, cancelDownload } = useDownloads();
|
||||||
|
const { showSuccess, showInfo } = useToast();
|
||||||
|
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const [selectedFilter, setSelectedFilter] = useState<'all' | 'downloading' | 'completed' | 'paused'>('all');
|
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 * as ScreenOrientation from 'expo-screen-orientation';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
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 FirstTimeWelcome from '../components/FirstTimeWelcome';
|
||||||
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
||||||
|
|
||||||
|
|
@ -111,6 +111,7 @@ const HomeScreen = () => {
|
||||||
const continueWatchingRef = useRef<ContinueWatchingRef>(null);
|
const continueWatchingRef = useRef<ContinueWatchingRef>(null);
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
const { lastUpdate } = useCatalogContext(); // Add catalog context to listen for addon changes
|
const { lastUpdate } = useCatalogContext(); // Add catalog context to listen for addon changes
|
||||||
|
const { showInfo } = useToast();
|
||||||
const [showHeroSection, setShowHeroSection] = useState(settings.showHeroSection);
|
const [showHeroSection, setShowHeroSection] = useState(settings.showHeroSection);
|
||||||
const [featuredContentSource, setFeaturedContentSource] = useState(settings.featuredContentSource);
|
const [featuredContentSource, setFeaturedContentSource] = useState(settings.featuredContentSource);
|
||||||
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
@ -351,7 +352,7 @@ const HomeScreen = () => {
|
||||||
await AsyncStorage.removeItem('showLoginHintToastOnce');
|
await AsyncStorage.removeItem('showLoginHintToastOnce');
|
||||||
hideTimer = setTimeout(() => setHintVisible(false), 2000);
|
hideTimer = setTimeout(() => setHintVisible(false), 2000);
|
||||||
// Also show a global toast for consistency across screens
|
// 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 {}
|
} catch {}
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { DeviceEventEmitter } from 'react-native';
|
import { DeviceEventEmitter } from 'react-native';
|
||||||
import { Share } from 'react-native';
|
import { Share } from 'react-native';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
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 DropUpMenu from '../components/home/DropUpMenu';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
|
|
@ -208,6 +208,7 @@ const LibraryScreen = () => {
|
||||||
const [filter, setFilter] = useState<'trakt' | 'movies' | 'series'>('movies');
|
const [filter, setFilter] = useState<'trakt' | 'movies' | 'series'>('movies');
|
||||||
const [showTraktContent, setShowTraktContent] = useState(false);
|
const [showTraktContent, setShowTraktContent] = useState(false);
|
||||||
const [selectedTraktFolder, setSelectedTraktFolder] = useState<string | null>(null);
|
const [selectedTraktFolder, setSelectedTraktFolder] = useState<string | null>(null);
|
||||||
|
const { showInfo, showError } = useToast();
|
||||||
// DropUpMenu state
|
// DropUpMenu state
|
||||||
const [menuVisible, setMenuVisible] = useState(false);
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
const [selectedItem, setSelectedItem] = useState<LibraryItem | null>(null);
|
const [selectedItem, setSelectedItem] = useState<LibraryItem | null>(null);
|
||||||
|
|
@ -1005,11 +1006,11 @@ const LibraryScreen = () => {
|
||||||
case 'library': {
|
case 'library': {
|
||||||
try {
|
try {
|
||||||
await catalogService.removeFromLibrary(selectedItem.type, selectedItem.id);
|
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)));
|
setLibraryItems(prev => prev.filter(item => !(item.id === selectedItem.id && item.type === selectedItem.type)));
|
||||||
setMenuVisible(false);
|
setMenuVisible(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Toast.error('Failed to update Library');
|
showError('Failed to update Library', 'Unable to remove item from library');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1019,7 +1020,7 @@ const LibraryScreen = () => {
|
||||||
const key = `watched:${selectedItem.type}:${selectedItem.id}`;
|
const key = `watched:${selectedItem.type}:${selectedItem.id}`;
|
||||||
const newWatched = !selectedItem.watched;
|
const newWatched = !selectedItem.watched;
|
||||||
await AsyncStorage.setItem(key, newWatched ? 'true' : 'false');
|
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
|
// Instantly update local state
|
||||||
setLibraryItems(prev => prev.map(item =>
|
setLibraryItems(prev => prev.map(item =>
|
||||||
item.id === selectedItem.id && item.type === selectedItem.type
|
item.id === selectedItem.id && item.type === selectedItem.type
|
||||||
|
|
@ -1027,7 +1028,7 @@ const LibraryScreen = () => {
|
||||||
: item
|
: item
|
||||||
));
|
));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Toast.error('Failed to update watched status');
|
showError('Failed to update watched status', 'Unable to update watched status');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ import QualityBadge from '../components/metadata/QualityBadge';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
import { isMkvStream } from '../utils/mkvDetection';
|
import { isMkvStream } from '../utils/mkvDetection';
|
||||||
import CustomAlert from '../components/CustomAlert';
|
import CustomAlert from '../components/CustomAlert';
|
||||||
import { Toast } from 'toastify-react-native';
|
import { useToast } from '../contexts/ToastContext';
|
||||||
import { useDownloads } from '../contexts/DownloadsContext';
|
import { useDownloads } from '../contexts/DownloadsContext';
|
||||||
import { PaperProvider } from 'react-native-paper';
|
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 { useSettings } = require('../hooks/useSettings');
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
const { startDownload } = useDownloads();
|
const { startDownload } = useDownloads();
|
||||||
|
const { showSuccess, showInfo } = useToast();
|
||||||
|
|
||||||
// Handle long press to copy stream URL to clipboard
|
// Handle long press to copy stream URL to clipboard
|
||||||
const handleLongPress = useCallback(async () => {
|
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
|
// Use toast for Android, custom alert for iOS
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
Toast.success('Stream URL copied to clipboard!', 'bottom');
|
showSuccess('URL Copied', 'Stream URL copied to clipboard!');
|
||||||
} else {
|
} else {
|
||||||
// iOS uses custom alert
|
// iOS uses custom alert
|
||||||
showAlert('Copied!', 'Stream URL has been copied to clipboard.');
|
showAlert('Copied!', 'Stream URL has been copied to clipboard.');
|
||||||
|
|
@ -244,13 +245,13 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Fallback: show URL in alert if clipboard fails
|
// Fallback: show URL in alert if clipboard fails
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
Toast.info(`Stream URL: ${stream.url}`, 'bottom');
|
showInfo('Stream URL', `Stream URL: ${stream.url}`);
|
||||||
} else {
|
} else {
|
||||||
showAlert('Stream URL', stream.url);
|
showAlert('Stream URL', stream.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [stream.url, showAlert]);
|
}, [stream.url, showAlert, showSuccess, showInfo]);
|
||||||
const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]);
|
const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]);
|
||||||
|
|
||||||
const streamInfo = useMemo(() => {
|
const streamInfo = useMemo(() => {
|
||||||
|
|
@ -513,6 +514,7 @@ export const StreamsScreen = () => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const { colors } = currentTheme;
|
const { colors } = currentTheme;
|
||||||
const { pauseTrailer, resumeTrailer } = useTrailer();
|
const { pauseTrailer, resumeTrailer } = useTrailer();
|
||||||
|
const { showSuccess, showInfo } = useToast();
|
||||||
|
|
||||||
// Add refs to prevent excessive updates and duplicate loads
|
// Add refs to prevent excessive updates and duplicate loads
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Linking
|
Linking
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Toast } from 'toastify-react-native';
|
import { useToast } from '../contexts/ToastContext';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NavigationProp } from '@react-navigation/native';
|
import { NavigationProp } from '@react-navigation/native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
|
|
@ -70,6 +70,7 @@ const UpdateScreen: React.FC = () => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const github = useGithubMajorUpdate();
|
const github = useGithubMajorUpdate();
|
||||||
|
const { showInfo } = useToast();
|
||||||
|
|
||||||
// CustomAlert state
|
// CustomAlert state
|
||||||
const [alertVisible, setAlertVisible] = useState(false);
|
const [alertVisible, setAlertVisible] = useState(false);
|
||||||
|
|
@ -152,7 +153,7 @@ const UpdateScreen: React.FC = () => {
|
||||||
// Also refresh GitHub section on mount (works in dev and prod)
|
// Also refresh GitHub section on mount (works in dev and prod)
|
||||||
try { github.refresh(); } catch {}
|
try { github.refresh(); } catch {}
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
try { Toast.info('Checking for updates…'); } catch {}
|
showInfo('Checking for Updates', 'Checking for updates…');
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue