new toast library
This commit is contained in:
parent
56654e1ced
commit
16c460cdc2
12 changed files with 251 additions and 84 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -56,3 +56,4 @@ src/screens/xavio.md
|
||||||
/KSPlayer
|
/KSPlayer
|
||||||
/exobase
|
/exobase
|
||||||
ffmpegreadme.md
|
ffmpegreadme.md
|
||||||
|
toast.md
|
||||||
|
|
|
||||||
104
package-lock.json
generated
104
package-lock.json
generated
|
|
@ -72,9 +72,11 @@
|
||||||
"react-native-screens": "~4.4.0",
|
"react-native-screens": "~4.4.0",
|
||||||
"react-native-svg": "15.8.0",
|
"react-native-svg": "15.8.0",
|
||||||
"react-native-url-polyfill": "^2.0.0",
|
"react-native-url-polyfill": "^2.0.0",
|
||||||
|
"react-native-vector-icons": "^10.3.0",
|
||||||
"react-native-video": "^6.12.0",
|
"react-native-video": "^6.12.0",
|
||||||
"react-native-web": "~0.19.13",
|
"react-native-web": "~0.19.13",
|
||||||
"react-native-wheel-color-picker": "^1.3.1"
|
"react-native-wheel-color-picker": "^1.3.1",
|
||||||
|
"toastify-react-native": "^7.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|
@ -12965,6 +12967,93 @@
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-vector-icons": {
|
||||||
|
"version": "10.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz",
|
||||||
|
"integrity": "sha512-IFQ0RE57819hOUdFvgK4FowM5aMXg7C7XKsuGLevqXkkIJatc3QopN0wYrb2IrzUgmdpfP+QVIbI3S6h7M0btw==",
|
||||||
|
"deprecated": "react-native-vector-icons package has moved to a new model of per-icon-family packages. See the https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md on how to migrate",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"yargs": "^16.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"fa-upgrade.sh": "bin/fa-upgrade.sh",
|
||||||
|
"fa5-upgrade": "bin/fa5-upgrade.sh",
|
||||||
|
"fa6-upgrade": "bin/fa6-upgrade.sh",
|
||||||
|
"generate-icon": "bin/generate-icon.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/yargs": {
|
||||||
|
"version": "16.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||||
|
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^20.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/yargs-parser": {
|
||||||
|
"version": "20.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||||
|
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-video": {
|
"node_modules/react-native-video": {
|
||||||
"version": "6.16.1",
|
"version": "6.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-6.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-6.16.1.tgz",
|
||||||
|
|
@ -14853,6 +14942,19 @@
|
||||||
"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",
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,11 @@
|
||||||
"react-native-screens": "~4.4.0",
|
"react-native-screens": "~4.4.0",
|
||||||
"react-native-svg": "15.8.0",
|
"react-native-svg": "15.8.0",
|
||||||
"react-native-url-polyfill": "^2.0.0",
|
"react-native-url-polyfill": "^2.0.0",
|
||||||
|
"react-native-vector-icons": "^10.3.0",
|
||||||
"react-native-video": "^6.12.0",
|
"react-native-video": "^6.12.0",
|
||||||
"react-native-web": "~0.19.13",
|
"react-native-web": "~0.19.13",
|
||||||
"react-native-wheel-color-picker": "^1.3.1"
|
"react-native-wheel-color-picker": "^1.3.1",
|
||||||
|
"toastify-react-native": "^7.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { toast } from '@backpackapp-io/react-native-toast';
|
import { Toast } from 'toastify-react-native';
|
||||||
import { DeviceEventEmitter } from 'react-native';
|
import { DeviceEventEmitter } from 'react-native';
|
||||||
import { View, TouchableOpacity, ActivityIndicator, StyleSheet, Dimensions, Platform, Text, Animated, Share } from 'react-native';
|
import { View, TouchableOpacity, ActivityIndicator, StyleSheet, Dimensions, Platform, Text, Animated, Share } from 'react-native';
|
||||||
import { Image as ExpoImage } from 'expo-image';
|
import { Image as ExpoImage } from 'expo-image';
|
||||||
|
|
@ -9,6 +9,8 @@ import { useSettings } from '../../hooks/useSettings';
|
||||||
import { catalogService, StreamingContent } from '../../services/catalogService';
|
import { catalogService, StreamingContent } from '../../services/catalogService';
|
||||||
import { DropUpMenu } from './DropUpMenu';
|
import { DropUpMenu } from './DropUpMenu';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import { storageService } from '../../services/storageService';
|
||||||
|
import { TraktService } from '../../services/traktService';
|
||||||
|
|
||||||
interface ContentItemProps {
|
interface ContentItemProps {
|
||||||
item: StreamingContent;
|
item: StreamingContent;
|
||||||
|
|
@ -116,28 +118,52 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe
|
||||||
onPress(item.id, item.type);
|
onPress(item.id, item.type);
|
||||||
}, [item.id, item.type, onPress]);
|
}, [item.id, item.type, onPress]);
|
||||||
|
|
||||||
const handleOptionSelect = useCallback((option: string) => {
|
const handleOptionSelect = useCallback(async (option: string) => {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case 'library':
|
case 'library':
|
||||||
if (inLibrary) {
|
if (inLibrary) {
|
||||||
catalogService.removeFromLibrary(item.type, item.id);
|
catalogService.removeFromLibrary(item.type, item.id);
|
||||||
toast('Removed from Library', { duration: 1200 });
|
Toast.info('Removed from Library');
|
||||||
} else {
|
} else {
|
||||||
catalogService.addToLibrary(item);
|
catalogService.addToLibrary(item);
|
||||||
toast('Added to Library', { duration: 1200 });
|
Toast.success('Added to Library');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'watched': {
|
case 'watched': {
|
||||||
setIsWatched(prevWatched => {
|
const targetWatched = !isWatched;
|
||||||
const newWatched = !prevWatched;
|
setIsWatched(targetWatched);
|
||||||
AsyncStorage.setItem(`watched:${item.type}:${item.id}`, newWatched ? 'true' : 'false');
|
try {
|
||||||
toast(newWatched ? 'Marked as Watched' : 'Marked as Unwatched', { duration: 1200 });
|
await AsyncStorage.setItem(`watched:${item.type}:${item.id}`, targetWatched ? 'true' : 'false');
|
||||||
// Fire a custom event so other screens can update
|
} catch {}
|
||||||
setTimeout(() => {
|
Toast.info(targetWatched ? 'Marked as Watched' : 'Marked as Unwatched');
|
||||||
DeviceEventEmitter.emit('watchedStatusChanged');
|
setTimeout(() => {
|
||||||
}, 100);
|
DeviceEventEmitter.emit('watchedStatusChanged');
|
||||||
return newWatched;
|
}, 100);
|
||||||
});
|
|
||||||
|
// Best-effort sync: record local progress and push to Trakt if available
|
||||||
|
if (targetWatched) {
|
||||||
|
try {
|
||||||
|
await storageService.setWatchProgress(
|
||||||
|
item.id,
|
||||||
|
item.type,
|
||||||
|
{ currentTime: 1, duration: 1, lastUpdated: Date.now() },
|
||||||
|
undefined,
|
||||||
|
{ forceNotify: true, forceWrite: true }
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (item.type === 'movie') {
|
||||||
|
try {
|
||||||
|
const trakt = TraktService.getInstance();
|
||||||
|
if (await trakt.isAuthenticated()) {
|
||||||
|
await trakt.addToWatchedMovies(item.id);
|
||||||
|
try {
|
||||||
|
await storageService.updateTraktSyncStatus(item.id, item.type, true, 100);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
setMenuVisible(false);
|
setMenuVisible(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +179,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [item, inLibrary]);
|
}, [item, inLibrary, isWatched]);
|
||||||
|
|
||||||
const handleMenuClose = useCallback(() => {
|
const handleMenuClose = useCallback(() => {
|
||||||
setMenuVisible(false);
|
setMenuVisible(false);
|
||||||
|
|
|
||||||
|
|
@ -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, ToastPosition } from '@backpackapp-io/react-native-toast';
|
import { Toast } from 'toastify-react-native';
|
||||||
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,19 +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('Unable to install the update. Please try again later or check your internet connection.', {
|
Toast.error('Unable to install the update. Please try again later or check your internet connection.');
|
||||||
duration: 3000,
|
|
||||||
position: ToastPosition.TOP,
|
|
||||||
});
|
|
||||||
// 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('An error occurred while installing the update. Please try again later.', {
|
Toast.error('An error occurred while installing the update. Please try again later.');
|
||||||
duration: 3000,
|
|
||||||
position: ToastPosition.TOP,
|
|
||||||
});
|
|
||||||
// Show popup again after error
|
// Show popup again after error
|
||||||
setShowUpdatePopup(true);
|
setShowUpdatePopup(true);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -141,12 +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 {
|
try { Toast.info('Update available — go to Settings → App Updates'); } catch {}
|
||||||
toast('Update available — go to Settings → App Updates', {
|
|
||||||
duration: 3000,
|
|
||||||
position: ToastPosition.TOP,
|
|
||||||
});
|
|
||||||
} catch {}
|
|
||||||
setShowUpdatePopup(false);
|
setShowUpdatePopup(false);
|
||||||
} else {
|
} else {
|
||||||
setShowUpdatePopup(true);
|
setShowUpdatePopup(true);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ 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 { Toasts } from '@backpackapp-io/react-native-toast';
|
import ToastManager from 'toastify-react-native';
|
||||||
import { PostHogProvider } from 'posthog-react-native';
|
import { PostHogProvider } from 'posthog-react-native';
|
||||||
|
|
||||||
// Import screens with their proper types
|
// Import screens with their proper types
|
||||||
|
|
@ -889,6 +889,7 @@ const customFadeInterpolator = ({ current, layouts }: any) => {
|
||||||
const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStackParamList }) => {
|
const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStackParamList }) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const { user, loading } = useAccount();
|
const { user, loading } = useAccount();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
// Handle Android-specific optimizations
|
// Handle Android-specific optimizations
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -1344,7 +1345,85 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</View>
|
</View>
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
<Toasts />
|
{/* 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 { toast } from '@backpackapp-io/react-native-toast';
|
import ToastManager, { Toast } from 'toastify-react-native';
|
||||||
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');
|
||||||
|
|
@ -144,21 +144,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);
|
Toast.error(msg);
|
||||||
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);
|
Toast.error(msg);
|
||||||
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);
|
Toast.error(msg);
|
||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -167,11 +167,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);
|
Toast.error(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);
|
Toast.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
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,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, ToastPosition } from '@backpackapp-io/react-native-toast';
|
import { Toast } from 'toastify-react-native';
|
||||||
import FirstTimeWelcome from '../components/FirstTimeWelcome';
|
import FirstTimeWelcome from '../components/FirstTimeWelcome';
|
||||||
import { imageCacheService } from '../services/imageCacheService';
|
import { imageCacheService } from '../services/imageCacheService';
|
||||||
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
||||||
|
|
@ -341,12 +341,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 {
|
try { Toast.info('You can sign in anytime from Settings → Account', 'bottom'); } catch {}
|
||||||
toast('You can sign in anytime from Settings → Account', {
|
|
||||||
duration: 1600,
|
|
||||||
position: ToastPosition.BOTTOM,
|
|
||||||
});
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
} 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 '@backpackapp-io/react-native-toast';
|
import { Toast } from 'toastify-react-native';
|
||||||
import DropUpMenu from '../components/home/DropUpMenu';
|
import DropUpMenu from '../components/home/DropUpMenu';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
|
|
@ -1002,11 +1002,11 @@ const LibraryScreen = () => {
|
||||||
case 'library': {
|
case 'library': {
|
||||||
try {
|
try {
|
||||||
await catalogService.removeFromLibrary(selectedItem.type, selectedItem.id);
|
await catalogService.removeFromLibrary(selectedItem.type, selectedItem.id);
|
||||||
toast('Removed from Library', { duration: 1200 });
|
Toast.info('Removed from 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('Failed to update Library', { duration: 1200 });
|
Toast.error('Failed to update Library');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1016,7 +1016,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(newWatched ? 'Marked as Watched' : 'Marked as Unwatched', { duration: 1200 });
|
Toast.info(newWatched ? 'Marked as Watched' : '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
|
||||||
|
|
|
||||||
|
|
@ -531,12 +531,6 @@ const SearchScreen = () => {
|
||||||
<MaterialIcons name="check-circle" size={20} color={currentTheme.colors.success || '#4CAF50'} />
|
<MaterialIcons name="check-circle" size={20} color={currentTheme.colors.success || '#4CAF50'} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{/* 'series'/'movie' text in original place */}
|
|
||||||
<View style={styles.itemTypeContainer}>
|
|
||||||
<Text style={[styles.itemTypeText, { color: currentTheme.colors.white }]}>
|
|
||||||
{item.type === 'movie' ? 'MOVIE' : 'SERIES'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
{item.imdbRating && (
|
{item.imdbRating && (
|
||||||
<View style={styles.ratingContainer}>
|
<View style={styles.ratingContainer}>
|
||||||
<MaterialIcons name="star" size={12} color="#FFC107" />
|
<MaterialIcons name="star" size={12} color="#FFC107" />
|
||||||
|
|
@ -1038,19 +1032,6 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
},
|
},
|
||||||
itemTypeContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 8,
|
|
||||||
left: 8,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
||||||
paddingHorizontal: 6,
|
|
||||||
paddingVertical: 2,
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
itemTypeText: {
|
|
||||||
fontSize: isTablet ? 7 : 8,
|
|
||||||
fontWeight: '700',
|
|
||||||
},
|
|
||||||
ratingContainer: {
|
ratingContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
|
|
|
||||||
|
|
@ -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, ToastPosition } from '@backpackapp-io/react-native-toast';
|
import { Toast } from 'toastify-react-native';
|
||||||
import { useDownloads } from '../contexts/DownloadsContext';
|
import { useDownloads } from '../contexts/DownloadsContext';
|
||||||
|
|
||||||
const TMDB_LOGO = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Tmdb.new.logo.svg/512px-Tmdb.new.logo.svg.png?20200406190906';
|
const TMDB_LOGO = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Tmdb.new.logo.svg/512px-Tmdb.new.logo.svg.png?20200406190906';
|
||||||
|
|
@ -233,10 +233,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('Stream URL copied to clipboard!', {
|
Toast.success('Stream URL copied to clipboard!', 'bottom');
|
||||||
duration: 2000,
|
|
||||||
position: ToastPosition.BOTTOM,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// iOS uses custom alert
|
// iOS uses custom alert
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -246,10 +243,7 @@ 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(`Stream URL: ${stream.url}`, {
|
Toast.info(`Stream URL: ${stream.url}`, 'bottom');
|
||||||
duration: 3000,
|
|
||||||
position: ToastPosition.BOTTOM,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showAlert('Stream URL', stream.url);
|
showAlert('Stream URL', stream.url);
|
||||||
|
|
@ -322,7 +316,7 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
||||||
url,
|
url,
|
||||||
headers: (stream.headers as any) || undefined,
|
headers: (stream.headers as any) || undefined,
|
||||||
});
|
});
|
||||||
toast('Download started', { duration: 1500, position: ToastPosition.BOTTOM });
|
Toast.success('Download started', 'bottom');
|
||||||
} catch {}
|
} catch {}
|
||||||
}, [startDownload, stream.url, stream.headers, streamInfo.quality, showAlert, stream.name, stream.title]);
|
}, [startDownload, stream.url, stream.headers, streamInfo.quality, showAlert, stream.name, stream.title]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Linking
|
Linking
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { toast, ToastPosition } from '@backpackapp-io/react-native-toast';
|
import { Toast } from 'toastify-react-native';
|
||||||
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';
|
||||||
|
|
@ -152,9 +152,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 {
|
try { Toast.info('Checking for updates…'); } catch {}
|
||||||
toast('Checking for updates…', { duration: 1200, position: ToastPosition.TOP });
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue