dependancy update

This commit is contained in:
tapframe 2025-12-15 00:49:15 +05:30
parent c3fbe31fd4
commit 181cdaecb5
14 changed files with 1312 additions and 1596 deletions

View file

@ -413,14 +413,12 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MobileVLCKit/MobileVLCKit.framework/MobileVLCKit",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MobileVLCKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
@ -477,7 +475,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio";
PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -508,8 +506,8 @@
"-lc++",
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app";
PRODUCT_NAME = "Nuvio";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";

View file

@ -21,7 +21,7 @@ platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
prepare_react_native_project!
target 'Nuvio' do
use_expo_modules!
use_expo_modules!(exclude: ['expo-libvlc-player'])
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];

View file

@ -1,17 +1,17 @@
PODS:
- DisplayCriteria (1.1.0)
- EASClient (1.0.7):
- EASClient (1.0.8):
- ExpoModulesCore
- EXApplication (7.0.7):
- EXApplication (7.0.8):
- ExpoModulesCore
- EXConstants (18.0.10):
- EXConstants (18.0.12):
- ExpoModulesCore
- EXJSONUtils (0.15.0)
- EXManifests (1.0.8):
- EXManifests (1.0.10):
- ExpoModulesCore
- EXNotifications (0.32.12):
- EXNotifications (0.32.15):
- ExpoModulesCore
- Expo (54.0.23):
- Expo (54.0.29):
- ExpoModulesCore
- hermes-engine
- RCTRequired
@ -36,15 +36,15 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- expo-dev-client (6.0.17):
- expo-dev-client (6.0.20):
- EXManifests
- expo-dev-launcher
- expo-dev-menu
- expo-dev-menu-interface
- EXUpdatesInterface
- expo-dev-launcher (6.0.17):
- expo-dev-launcher (6.0.20):
- EXManifests
- expo-dev-launcher/Main (= 6.0.17)
- expo-dev-launcher/Main (= 6.0.20)
- expo-dev-menu
- expo-dev-menu-interface
- ExpoModulesCore
@ -73,7 +73,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- expo-dev-launcher/Main (6.0.17):
- expo-dev-launcher/Main (6.0.20):
- EXManifests
- expo-dev-launcher/Unsafe
- expo-dev-menu
@ -104,7 +104,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- expo-dev-launcher/Unsafe (6.0.17):
- expo-dev-launcher/Unsafe (6.0.20):
- EXManifests
- expo-dev-menu
- expo-dev-menu-interface
@ -134,9 +134,9 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- expo-dev-menu (7.0.16):
- expo-dev-menu/Main (= 7.0.16)
- expo-dev-menu/ReactNativeCompatibles (= 7.0.16)
- expo-dev-menu (7.0.18):
- expo-dev-menu/Main (= 7.0.18)
- expo-dev-menu/ReactNativeCompatibles (= 7.0.18)
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -159,7 +159,7 @@ PODS:
- ReactNativeDependencies
- Yoga
- expo-dev-menu-interface (2.0.0)
- expo-dev-menu/Main (7.0.16):
- expo-dev-menu/Main (7.0.18):
- EXManifests
- expo-dev-menu-interface
- ExpoModulesCore
@ -185,7 +185,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- expo-dev-menu/ReactNativeCompatibles (7.0.16):
- expo-dev-menu/ReactNativeCompatibles (7.0.18):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -207,38 +207,35 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- ExpoAsset (12.0.9):
- ExpoAsset (12.0.11):
- ExpoModulesCore
- ExpoBlur (15.0.7):
- ExpoBlur (15.0.8):
- ExpoModulesCore
- ExpoBrightness (14.0.7):
- ExpoBrightness (14.0.8):
- ExpoModulesCore
- ExpoCrypto (15.0.7):
- ExpoCrypto (15.0.8):
- ExpoModulesCore
- ExpoDevice (8.0.9):
- ExpoDevice (8.0.10):
- ExpoModulesCore
- ExpoDocumentPicker (14.0.7):
- ExpoDocumentPicker (14.0.8):
- ExpoModulesCore
- ExpoFileSystem (19.0.17):
- ExpoFileSystem (19.0.21):
- ExpoModulesCore
- ExpoFont (14.0.9):
- ExpoFont (14.0.10):
- ExpoModulesCore
- ExpoGlassEffect (0.1.7):
- ExpoGlassEffect (0.1.8):
- ExpoModulesCore
- ExpoHaptics (15.0.7):
- ExpoHaptics (15.0.8):
- ExpoModulesCore
- ExpoKeepAwake (15.0.7):
- ExpoKeepAwake (15.0.8):
- ExpoModulesCore
- ExpoLibVlcPlayer (2.2.3):
- ExpoLinearGradient (15.0.8):
- ExpoModulesCore
- MobileVLCKit (= 3.6.1b1)
- ExpoLinearGradient (15.0.7):
- ExpoLinking (8.0.10):
- ExpoModulesCore
- ExpoLinking (8.0.8):
- ExpoLocalization (17.0.8):
- ExpoModulesCore
- ExpoLocalization (17.0.7):
- ExpoModulesCore
- ExpoModulesCore (3.0.25):
- ExpoModulesCore (3.0.29):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -263,7 +260,7 @@ PODS:
- Yoga
- ExpoRandom (14.0.1):
- ExpoModulesCore
- ExpoScreenOrientation (9.0.7):
- ExpoScreenOrientation (9.0.8):
- ExpoModulesCore
- hermes-engine
- RCTRequired
@ -286,14 +283,14 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- ExpoSharing (14.0.7):
- ExpoSharing (14.0.8):
- ExpoModulesCore
- ExpoSystemUI (6.0.8):
- ExpoSystemUI (6.0.9):
- ExpoModulesCore
- ExpoWebBrowser (15.0.9):
- ExpoWebBrowser (15.0.10):
- ExpoModulesCore
- EXStructuredHeaders (5.0.0)
- EXUpdates (29.0.12):
- EXUpdates (29.0.15):
- EASClient
- EXManifests
- ExpoModulesCore
@ -332,7 +329,7 @@ PODS:
- hermes-engine (0.81.4):
- hermes-engine/Pre-built (= 0.81.4)
- hermes-engine/Pre-built (0.81.4)
- ImageColors (2.5.0):
- ImageColors (2.5.1):
- ExpoModulesCore
- KSPlayer (1.1.0):
- KSPlayer/Audio (= 1.1.0)
@ -406,8 +403,7 @@ PODS:
- ReactNativeDependencies
- Yoga
- MMKVCore (2.2.4)
- MobileVLCKit (3.6.1b1)
- NitroMmkv (4.0.0):
- NitroMmkv (4.1.0):
- hermes-engine
- MMKVCore (= 2.2.4)
- NitroModules
@ -432,7 +428,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- NitroModules (0.31.6):
- NitroModules (0.31.10):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1758,7 +1754,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-bottom-tabs (1.0.2):
- react-native-bottom-tabs (1.1.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1770,7 +1766,7 @@ PODS:
- React-graphics
- React-ImageManager
- React-jsi
- react-native-bottom-tabs/common (= 1.0.2)
- react-native-bottom-tabs/common (= 1.1.0)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
@ -1782,7 +1778,7 @@ PODS:
- ReactNativeDependencies
- SwiftUIIntrospect (~> 1.0)
- Yoga
- react-native-bottom-tabs/common (1.0.2):
- react-native-bottom-tabs/common (1.1.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1904,30 +1900,6 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-skia (2.3.13):
- hermes-engine
- RCTRequired
- RCTTypeSafety
- React
- React-callinvoker
- React-Core
- React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-slider (5.1.1):
- hermes-engine
- RCTRequired
@ -1973,7 +1945,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-video (6.17.0):
- react-native-video (6.18.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -1985,7 +1957,7 @@ PODS:
- React-graphics
- React-ImageManager
- React-jsi
- react-native-video/Video (= 6.17.0)
- react-native-video/Video (= 6.18.0)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
@ -1996,7 +1968,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-video/Fabric (6.17.0):
- react-native-video/Fabric (6.18.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2018,7 +1990,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-video/Video (6.17.0):
- react-native-video/Video (6.18.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2463,7 +2435,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNReanimated (4.1.5):
- RNReanimated (4.2.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2485,10 +2457,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNReanimated/reanimated (= 4.1.5)
- RNReanimated/reanimated (= 4.2.0)
- RNWorklets
- Yoga
- RNReanimated/reanimated (4.1.5):
- RNReanimated/reanimated (4.2.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2510,10 +2482,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNReanimated/reanimated/apple (= 4.1.5)
- RNReanimated/reanimated/apple (= 4.2.0)
- RNWorklets
- Yoga
- RNReanimated/reanimated/apple (4.1.5):
- RNReanimated/reanimated/apple (4.2.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2584,7 +2556,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNSentry (7.6.0):
- RNSentry (7.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2606,9 +2578,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Sentry/HybridSDK (= 8.57.2)
- Sentry/HybridSDK (= 8.57.3)
- Yoga
- RNSVG (15.15.0):
- RNSVG (15.15.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2629,9 +2601,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNSVG/common (= 15.15.0)
- RNSVG/common (= 15.15.1)
- Yoga
- RNSVG/common (15.15.0):
- RNSVG/common (15.15.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2675,7 +2647,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNWorklets (0.6.1):
- RNWorklets (0.7.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2697,9 +2669,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNWorklets/worklets (= 0.6.1)
- RNWorklets/worklets (= 0.7.1)
- Yoga
- RNWorklets/worklets (0.6.1):
- RNWorklets/worklets (0.7.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2721,9 +2693,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNWorklets/worklets/apple (= 0.6.1)
- RNWorklets/worklets/apple (= 0.7.1)
- Yoga
- RNWorklets/worklets/apple (0.6.1):
- RNWorklets/worklets/apple (0.7.1):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -2746,9 +2718,9 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- SDWebImage (5.21.3):
- SDWebImage/Core (= 5.21.3)
- SDWebImage/Core (5.21.3)
- SDWebImage (5.21.5):
- SDWebImage/Core (= 5.21.5)
- SDWebImage/Core (5.21.5)
- SDWebImageAVIFCoder (0.11.1):
- libavif/core (>= 0.11.0)
- SDWebImage (~> 5.10)
@ -2757,7 +2729,7 @@ PODS:
- SDWebImageWebPCoder (0.15.0):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- Sentry/HybridSDK (8.57.2)
- Sentry/HybridSDK (8.57.3)
- SwiftUIIntrospect (1.3.0)
- Yoga (0.0.0)
@ -2785,7 +2757,6 @@ DEPENDENCIES:
- ExpoGlassEffect (from `../node_modules/expo-glass-effect/ios`)
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLibVlcPlayer (from `../node_modules/expo-libvlc-player/ios`)
- ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
- ExpoLinking (from `../node_modules/expo-linking/ios`)
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
@ -2848,7 +2819,6 @@ DEPENDENCIES:
- react-native-google-cast (from `../node_modules/react-native-google-cast`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-skia (from `../node_modules/@shopify/react-native-skia`)"
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-video (from `../node_modules/react-native-video`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
@ -2901,7 +2871,6 @@ SPEC REPOS:
- libwebp
- lottie-ios
- MMKVCore
- MobileVLCKit
- PromisesObjC
- ReachabilitySwift
- SDWebImage
@ -2959,8 +2928,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-haptics/ios"
ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios"
ExpoLibVlcPlayer:
:path: "../node_modules/expo-libvlc-player/ios"
ExpoLinearGradient:
:path: "../node_modules/expo-linear-gradient/ios"
ExpoLinking:
@ -3087,8 +3054,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/netinfo"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-skia:
:path: "../node_modules/@shopify/react-native-skia"
react-native-slider:
:path: "../node_modules/@react-native-community/slider"
react-native-video:
@ -3178,13 +3143,13 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
DisplayCriteria:
:commit: cbc74996afb55e096bf1ff240f07d1d206ac86df
:commit: 101cceed0f2d9b6833ee69cf29b65a042de720a3
:git: https://github.com/kingslay/KSPlayer.git
FFmpegKit:
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
:git: https://github.com/kingslay/FFmpegKit.git
KSPlayer:
:commit: cbc74996afb55e096bf1ff240f07d1d206ac86df
:commit: 101cceed0f2d9b6833ee69cf29b65a042de720a3
:git: https://github.com/kingslay/KSPlayer.git
Libass:
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
@ -3192,46 +3157,45 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
DisplayCriteria: bb0a90faf14b30848bc50ac0516340ce50164187
EASClient: 68127f1248d2b25fdc82dbbfb17be95d1c4700be
EXApplication: 296622817d459f46b6c5fe8691f4aac44d2b79e7
EXConstants: fd688cef4e401dcf798a021cfb5d87c890c30ba3
EASClient: 40dd9e740684782610c49becab2643782ea1a20c
EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f
EXConstants: 805f35b1b295c542ca6acce836f21a1f9ee104d5
EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd
EXManifests: 224345a575fca389073c416297b6348163f28d1a
EXNotifications: 7cff475adb5d7a255a9ea46bbd2589cb3b454506
Expo: fb09185d798c2876a4c5ca89a5c6b8b72b6dbecf
expo-dev-client: b6e7b4f4063ae44b5e68cc6a8bcc0c79c3037c1a
expo-dev-launcher: c8813e0064e8768d676ee490c0f7ef1784d70b98
expo-dev-menu: 0a1194185c9eec1da0e507b734180775363be442
EXManifests: a8d97683e5c7a3b026ffbd58559c64dc655b747b
EXNotifications: 983f04ad4ad879b181179e326bf220541e478386
Expo: 8fa2204bf8483fe546b4ec87c90d3ca189afc8db
expo-dev-client: 425ee077d6754a98cfe3a2e2410d29b440b24c9d
expo-dev-launcher: a4f4cdef064ab1fb8621e5b8c7c457cd6e9568c3
expo-dev-menu: 05b18812110c175814c6af0d09dd658abcc5e00d
expo-dev-menu-interface: 600df12ea01efecdd822daaf13cc0ac091775533
ExpoAsset: 9ba6fbd677fb8e241a3899ac00fa735bc911eadf
ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f
ExpoBrightness: 32672952bf8b152d0cceaf8ec9f1def3a9a5e0d9
ExpoCrypto: c1fbce112d1b6b79652bbe380b4fd4cc91676595
ExpoDevice: 148accb4071873d19fba80a2506c58ffa433d620
ExpoDocumentPicker: 2200eefc2817f19315fa18f0147e0b80ece86926
ExpoFileSystem: b79eadbda7b7f285f378f95f959cc9313a1c9c61
ExpoFont: cf9d90ec1d3b97c4f513211905724c8171f82961
ExpoGlassEffect: 265fa3d75b46bc58262e4dfa513135fa9dfe4aac
ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84
ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe
ExpoLibVlcPlayer: 6b4a27f54f5300550227cffcf25cc88ab4f6c7c9
ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27
ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d
ExpoLocalization: b852a5d8ec14c5349c1593eca87896b5b3ebfcca
ExpoModulesCore: aa1a8e103d41de84baa5d7c6b98314e2230f1eef
ExpoAsset: 23a958e97d3d340919fe6774db35d563241e6c03
ExpoBlur: b90747a3f22a8b6ceffd9cb0dc41a4184efdc656
ExpoBrightness: 46c980463e8a54b9ce77f923c4bff0bb0c9526e0
ExpoCrypto: b6105ebaa15d6b38a811e71e43b52cd934945322
ExpoDevice: 6327c3c200816795708885adf540d26ecab83d1a
ExpoDocumentPicker: 7cd9e71a0f66fb19eb0a586d6f26eee1284692e0
ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063
ExpoFont: 35ac6191ed86bbf56b3ebd2d9154eda9fad5b509
ExpoGlassEffect: 8ce45eca31f12e949e23a4ee13e2bfb59e9b0785
ExpoHaptics: d3a6375d8dcc3a1083d003bc2298ff654fafb536
ExpoKeepAwake: 55f75eca6499bb9e4231ebad6f3e9cb8f99c0296
ExpoLinearGradient: 809102bdb979f590083af49f7fa4805cd931bd58
ExpoLinking: f4c4a351523da72a6bfa7e1f4ca92aee1043a3ca
ExpoLocalization: d9168d5300a5b03e5e78b986124d11fb6ec3ebbd
ExpoModulesCore: f3da4f1ab5a8375d0beafab763739dbee8446583
ExpoRandom: d1444df65007bdd4070009efd5dab18e20bf0f00
ExpoScreenOrientation: ef9ab3fb85c8a8ff57d52aa169b750aca03f0f4c
ExpoSharing: 032c01bb034319e2374badf082ae935be866d2e9
ExpoSystemUI: 2761aa6875849af83286364811d46e8ed8ea64c7
ExpoWebBrowser: b973e1351fdcf5fec0c400997b1851f5a8219ec3
ExpoScreenOrientation: c68bd20f210d0616960638c787889e07787e5adb
ExpoSharing: 0d983394ed4a80334bab5a0d5384f75710feb7e8
ExpoSystemUI: 2ad325f361a2fcd96a464e8574e19935c461c9cc
ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52
EXStructuredHeaders: c951e77f2d936f88637421e9588c976da5827368
EXUpdates: ef83273afc231a627b170358c90689ac30a4429d
EXUpdates: f20abbc8a9f4e150656fe88126d52f52d4e7793f
EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734
FBLazyVector: 9e0cd874afd81d9a4d36679daca991b58b260d42
FFmpegKit: 3885085fbbc320745838ee4c8a1f9c5e5953dab2
google-cast-sdk: 32f65af50d164e3c475e79ad123db3cc26fbcd37
hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394
ImageColors: 51cd79f7a9d2524b7a681c660b0a50574085563b
ImageColors: e12eb73e29bc1feaa3c228db8c174a1b25acb59d
KSPlayer: f163ac6195f240b6fa5b8225aeb39ec811a70c62
Libass: e88af2324e1217e3a4c8bdc675f6f23a9dfc7677
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
@ -3240,9 +3204,8 @@ SPEC CHECKSUMS:
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c
MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df
MobileVLCKit: 2d9c7c373393ae43086aeeff890bf0b1afc15c5c
NitroMmkv: 7fe66a61d5acab6516098a64f42af575595e7566
NitroModules: a672a4b7470810b8dae8fc2ff91eabaa2e1eff7d
NitroMmkv: 4af10c70043b4c3cded3f16547627c7d9d8e3b8b
NitroModules: a71a5ab2911caf79e45170e6e12475b5260a12d0
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RCTDeprecation: 7487d6dda857ccd4cb3dd6ecfccdc3170e85dcbc
RCTRequired: 54128b7df8be566881d48c7234724a78cb9b6157
@ -3279,15 +3242,14 @@ SPEC CHECKSUMS:
React-Mapbuffer: fbe1da882a187e5898bdf125e1cc6e603d27ecae
React-microtasksnativemodule: 76905804171d8ccbe69329fc84c57eb7934add7f
react-native-blur: 1b00ef07fe0efdc0c40b37139a5268ccad73c72d
react-native-bottom-tabs: b6459855502662d724d84b7edc937ea2b5a988ff
react-native-bottom-tabs: bcb70e4fae95fc9da0da875f7414acda26dfc551
react-native-device-brightness: 1a997350d060c3df9f303b1df84a4f7c5cbeb924
react-native-get-random-values: a603782b2b222a34533c66371614790282dba3f1
react-native-google-cast: 7be68a5d0b7eeb95a5924c3ecef8d319ef6c0a44
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
react-native-safe-area-context: 37e680fc4cace3c0030ee46e8987d24f5d3bdab2
react-native-skia: e386a7d05f10c87d2b0f9bf0165a6b59bc0c7410
react-native-slider: f954578344106f0a732a4358ce3a3e11015eb6e1
react-native-video: 5d9635903e562e0c5eb47c5fa401f1c807d6e068
react-native-video: f5982e21efab0dc356d92541a8a9e19581307f58
React-NativeModulesApple: a9464983ccc0f66f45e93558671f60fc7536e438
React-oscompat: 73db7dbc80edef36a9d6ed3c6c4e1724ead4236d
React-perflogger: 123272debf907cc423962adafcf4513320e43757
@ -3322,20 +3284,20 @@ SPEC CHECKSUMS:
RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca
RNFastImage: 2d36f4cfed9b2342f94f8591c8be69dd047ac67c
RNGestureHandler: 723f29dac55e25f109d263ed65cecc4b9c4bd46a
RNReanimated: 1442a577e066e662f0ce1cd1864a65c8e547aee0
RNReanimated: e1c71e6e693a66b203ae98773347b625d3cc85ee
RNScreens: 61c18865ab074f4d995ac8d7cf5060522a649d05
RNSentry: be6d501966b60b30547abe59ea86626d80ad2680
RNSVG: 99ab6158011aece12019b236f168faa7a1e41af6
RNSentry: 1d7b9fdae7a01ad8f9053335b5d44e75c39a955e
RNSVG: cf9ae78f2edf2988242c71a6392d15ff7dd62522
RNVectorIcons: 4351544f100d4f12cac156a7c13399e60bab3e26
RNWorklets: 54d8dffb7f645873a58484658ddfd4bd1a9a0bc1
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
RNWorklets: 9eb6d567fa43984e96b6924a6df504b8a15980cd
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
Sentry: 83a3814c3ca042874b39c5c5bdffb6570d4d760e
Sentry: c643eb180df401dd8c734c5036ddd9dd9218daa6
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
Yoga: 051f086b5ccf465ff2ed38a2cf5a558ae01aaaa1
PODFILE CHECKSUM: 1db7b3713ca6ad8568e4bdf6b72b92b72ee8199d
PODFILE CHECKSUM: 7c74c9cd2c7f3df7ab68b4284d9f324282e54542
COCOAPODS: 1.16.2

View file

@ -2,4 +2,4 @@
"expo.jsEngine": "hermes",
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
"newArchEnabled": "true"
}
}

1623
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,6 @@
"@react-navigation/stack": "^7.2.10",
"@sentry/react-native": "^7.6.0",
"@shopify/flash-list": "^2.2.0",
"@shopify/react-native-skia": "^2.3.13",
"@types/lodash": "^4.17.16",
"@types/react-native-video": "^5.0.20",
"axios": "^1.12.2",
@ -78,7 +77,7 @@
"react-native-mmkv": "^4.0.0",
"react-native-nitro-modules": "^0.31.2",
"react-native-paper": "^5.14.5",
"react-native-reanimated": "^4.1.1",
"react-native-reanimated": "^4.2.0",
"react-native-reanimated-carousel": "^4.0.3",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "^4.18.0",
@ -88,7 +87,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"
"react-native-worklets": "^0.7.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",

View file

@ -41,13 +41,13 @@ const calculatePosterLayout = (screenWidth: number) => {
const MAX_POSTER_WIDTH = 130; // Reduced maximum for more posters
const LEFT_PADDING = 16; // Left padding
const SPACING = 8; // Space between posters
// Calculate available width for posters (reserve space for left padding)
const availableWidth = screenWidth - LEFT_PADDING;
// Try different numbers of full posters to find the best fit
let bestLayout = { numFullPosters: 3, posterWidth: 120 };
for (let n = 3; n <= 6; n++) {
// Calculate poster width needed for N full posters + 0.25 partial poster
// Formula: N * posterWidth + (N-1) * spacing + 0.25 * posterWidth = availableWidth - rightPadding
@ -55,12 +55,12 @@ const calculatePosterLayout = (screenWidth: number) => {
// We'll use minimal right padding (8px) to maximize space
const usableWidth = availableWidth - 8;
const posterWidth = (usableWidth - (n - 1) * SPACING) / (n + 0.25);
if (posterWidth >= MIN_POSTER_WIDTH && posterWidth <= MAX_POSTER_WIDTH) {
bestLayout = { numFullPosters: n, posterWidth };
}
}
return {
numFullPosters: bestLayout.numFullPosters,
posterWidth: bestLayout.posterWidth,
@ -82,8 +82,8 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
const renderContentItem = useCallback(({ item }: { item: StreamingContent, index: number }) => {
return (
<ContentItem
item={item}
<ContentItem
item={item}
onPress={handleContentPress}
/>
);
@ -112,9 +112,8 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
}, [itemWidth, separatorWidth, isTV, isLargeTablet, isTablet]);
return (
<Animated.View
<View
style={styles.catalogContainer}
entering={FadeIn.duration(400)}
>
<View style={[
styles.catalogHeader,
@ -145,7 +144,7 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
/>
</View>
<TouchableOpacity
onPress={() =>
onPress={() =>
navigation.navigate('Catalog', {
id: catalog.id,
type: catalog.type,
@ -176,7 +175,7 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
/>
</TouchableOpacity>
</View>
<FlatList
data={catalog.items}
renderItem={renderContentItem}
@ -202,7 +201,7 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
windowSize={isTV ? 4 : isLargeTablet ? 4 : 3}
updateCellsBatchingPeriod={50}
/>
</Animated.View>
</View>
);
};
@ -262,8 +261,8 @@ export default React.memo(CatalogSection, (prevProps, nextProps) => {
prevProps.catalog.name === nextProps.catalog.name &&
prevProps.catalog.items.length === nextProps.catalog.items.length &&
// Deep compare the first few items to detect changes
prevProps.catalog.items.slice(0, 3).every((item, index) =>
nextProps.catalog.items[index] &&
prevProps.catalog.items.slice(0, 3).every((item, index) =>
nextProps.catalog.items[index] &&
item.id === nextProps.catalog.items[index].id &&
item.poster === nextProps.catalog.items[index].poster
)

View file

@ -1157,9 +1157,8 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
}
return (
<Animated.View
<View
style={styles.container}
entering={FadeIn.duration(350)}
>
<View style={[styles.header, { paddingHorizontal: horizontalPadding }]}>
<View style={styles.titleContainer}>
@ -1207,7 +1206,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
actions={alertActions}
onClose={() => setAlertVisible(false)}
/>
</Animated.View>
</View>
);
});

View file

@ -95,7 +95,7 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
// Optimized: update background as soon as scroll starts, without waiting for momentum end
const scrollX = useSharedValue(0);
const paginationProgress = useSharedValue(0);
// Parallel image prefetch: start fetching banners and logos as soon as data arrives
const itemsToPreload = useMemo(() => data.slice(0, 12), [data]);
useEffect(() => {
@ -121,7 +121,7 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
// no-op: prefetch is best-effort
}
}, [itemsToPreload]);
// Comprehensive reset when component mounts/remounts to prevent glitching
useEffect(() => {
// Start at the first real item for looping
@ -158,7 +158,7 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
}, 50);
return () => clearTimeout(timer);
}, [windowWidth, windowHeight, interval, loopingEnabled]);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollX.value = event.contentOffset.x;
@ -192,12 +192,12 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
},
(idx, prevIdx) => {
if (idx == null || idx === prevIdx) return;
// Debounce updates to reduce JS bridge crossings
const now = Date.now();
if (now - lastIndexUpdateRef.current < 100) return; // 100ms debounce
lastIndexUpdateRef.current = now;
// Clamp to bounds to avoid out-of-range access
const clamped = Math.max(0, Math.min(idx, data.length - 1));
runOnJS(setActiveIndex)(clamped);
@ -289,11 +289,11 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
}
// Memoized background component with improved timing
const BackgroundImage = React.memo(({
item,
const BackgroundImage = React.memo(({
item,
insets
}: {
item: StreamingContent;
}: {
item: StreamingContent;
insets: any;
}) => {
return (
@ -317,7 +317,7 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
) : (
<>
<FastImage
source={{
source={{
uri: item.banner || item.poster,
priority: FastImage.priority.low,
cache: FastImage.cacheControl.immutable
@ -352,15 +352,15 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
if (!hasData) return null;
return (
<Animated.View entering={FadeIn.duration(150).easing(Easing.out(Easing.cubic))}>
<View>
<Animated.View style={[styles.container as ViewStyle, { paddingTop: 12 + effectiveTopOffset }]}>
{/* Removed preload images for performance - let FastImage cache handle it naturally */}
{settings.enableHomeHeroBackground && data[activeIndex] && (
<BackgroundImage
item={data[activeIndex]}
insets={insets}
/>
)}
{settings.enableHomeHeroBackground && data[activeIndex] && (
<BackgroundImage
item={data[activeIndex]}
insets={insets}
/>
)}
{/* Bottom blend to HomeScreen background (not the card) */}
{settings.enableHomeHeroBackground && (
<LinearGradient
@ -444,7 +444,7 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
}}
/>
</View>
</Animated.View>
</View>
);
};
@ -467,13 +467,13 @@ interface CarouselCardProps {
const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFailed, onLogoError, onPressInfo, scrollX, index, flipped, onToggleFlip, interval, cardWidth, cardHeight, isTablet }) => {
const [bannerLoaded, setBannerLoaded] = useState(false);
const [logoLoaded, setLogoLoaded] = useState(false);
const bannerOpacity = useSharedValue(0);
const logoOpacity = useSharedValue(0);
const genresOpacity = useSharedValue(0);
const actionsOpacity = useSharedValue(0);
const isFlipped = useSharedValue(flipped ? 1 : 0);
// Reset animations when component mounts/remounts to prevent glitching
useEffect(() => {
bannerOpacity.value = 0;
@ -484,17 +484,17 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
setBannerLoaded(false);
setLogoLoaded(false);
}, [item.id]);
const inputRange = [
(index - 1) * interval,
index * interval,
(index + 1) * interval,
];
const bannerAnimatedStyle = useAnimatedStyle(() => ({
opacity: bannerOpacity.value,
}));
const logoAnimatedStyle = useAnimatedStyle(() => ({
opacity: logoOpacity.value,
}));
@ -538,52 +538,52 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
const translateX = scrollX.value;
const cardOffset = index * interval;
const distance = Math.abs(translateX - cardOffset);
// AGGRESSIVE early exit for cards far from center
if (distance > interval * 1.2) {
return { opacity: 0 };
}
const maxDistance = interval * 0.5;
const progress = Math.min(distance / maxDistance, 1);
const opacity = 1 - progress;
const clampedOpacity = Math.max(0, Math.min(1, opacity));
return {
opacity: clampedOpacity,
};
});
// ULTRA-OPTIMIZED: Only animate center card and ±1 neighbors
const cardAnimatedStyle = useAnimatedStyle(() => {
const translateX = scrollX.value;
const cardOffset = index * interval;
const distance = Math.abs(translateX - cardOffset);
// AGGRESSIVE early exit for cards far from center
if (distance > interval * 1.5) {
return {
transform: [{ scale: isTablet ? 0.95 : 0.9 }],
opacity: isTablet ? 0.85 : 0.7
return {
transform: [{ scale: isTablet ? 0.95 : 0.9 }],
opacity: isTablet ? 0.85 : 0.7
};
}
const maxDistance = interval;
// Scale animation based on distance from center
const scale = 1 - (distance / maxDistance) * 0.1;
const clampedScale = Math.max(isTablet ? 0.95 : 0.9, Math.min(1, scale));
// Opacity animation for cards that are far from center
const opacity = 1 - (distance / maxDistance) * 0.3;
const clampedOpacity = Math.max(isTablet ? 0.85 : 0.7, Math.min(1, opacity));
return {
transform: [{ scale: clampedScale }],
opacity: clampedOpacity,
};
});
// TEMPORARILY DISABLED FOR PERFORMANCE TESTING
// const bannerParallaxStyle = useAnimatedStyle(() => {
// const translateX = scrollX.value;
@ -597,7 +597,7 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
// transform: [{ translateX: parallaxOffset }],
// };
// });
// TEMPORARILY DISABLED FOR PERFORMANCE TESTING
// const infoParallaxStyle = useAnimatedStyle(() => {
// const translateX = scrollX.value;
@ -618,21 +618,21 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
// opacity: clampedOpacity,
// };
// });
useEffect(() => {
if (bannerLoaded) {
bannerOpacity.value = withTiming(1, {
duration: 250,
easing: Easing.out(Easing.ease)
bannerOpacity.value = withTiming(1, {
duration: 250,
easing: Easing.out(Easing.ease)
});
}
}, [bannerLoaded]);
useEffect(() => {
if (logoLoaded) {
logoOpacity.value = withTiming(1, {
duration: 300,
easing: Easing.out(Easing.ease)
logoOpacity.value = withTiming(1, {
duration: 300,
easing: Easing.out(Easing.ease)
});
}
}, [logoLoaded]);
@ -757,23 +757,23 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
</View>
) : (
<View style={styles.titleOverlay as ViewStyle} pointerEvents="none">
<Animated.View entering={FadeIn.duration(300)}>
<View>
<Text style={[styles.title as TextStyle, { color: colors.highEmphasis, textAlign: 'center' }]} numberOfLines={1}>
{item.name}
</Text>
</Animated.View>
</View>
</View>
)}
{item.genres && (
<View style={styles.genresOverlay as ViewStyle} pointerEvents="none">
<Animated.View entering={FadeIn.duration(400).delay(100)}>
<View>
<Animated.Text
style={[styles.genres as TextStyle, { color: colors.mediumEmphasis, textAlign: 'center' }, overlayAnimatedStyle]}
numberOfLines={1}
>
{item.genres.slice(0, 3).join(' • ')}
</Animated.Text>
</Animated.View>
</View>
</View>
)}
</TouchableOpacity>
@ -980,7 +980,7 @@ const styles = StyleSheet.create({
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.18)'
},
info: {
position: 'absolute',
left: 0,

View file

@ -200,7 +200,7 @@ const CompactCommentCard: React.FC<{
// Enhanced responsive sizing for tablets and TV screens
const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('window').height;
// Determine device type based on width
const getDeviceType = useCallback(() => {
if (deviceWidth >= BREAKPOINTS.tv) return 'tv';
@ -208,13 +208,13 @@ const CompactCommentCard: React.FC<{
if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet';
return 'phone';
}, [deviceWidth]);
const deviceType = getDeviceType();
const isTablet = deviceType === 'tablet';
const isLargeTablet = deviceType === 'largeTablet';
const isTV = deviceType === 'tv';
const isLargeScreen = isTablet || isLargeTablet || isTV;
// Enhanced comment card sizing
const commentCardWidth = useMemo(() => {
switch (deviceType) {
@ -228,7 +228,7 @@ const CompactCommentCard: React.FC<{
return 280; // phone
}
}, [deviceType]);
const commentCardHeight = useMemo(() => {
switch (deviceType) {
case 'tv':
@ -241,7 +241,7 @@ const CompactCommentCard: React.FC<{
return 170; // phone
}
}, [deviceType]);
const commentCardSpacing = useMemo(() => {
switch (deviceType) {
case 'tv':
@ -354,156 +354,156 @@ const CompactCommentCard: React.FC<{
}}
activeOpacity={1}
>
{/* Trakt Icon - Top Right Corner */}
<View style={styles.traktIconContainer}>
<TraktIcon width={isTV ? 20 : isLargeTablet ? 18 : isTablet ? 16 : 16} height={isTV ? 20 : isLargeTablet ? 18 : isTablet ? 16 : 16} />
</View>
{/* Header Section - Fixed at top */}
<View style={[
styles.compactHeader,
{
marginBottom: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8
}
]}>
<View style={styles.usernameContainer}>
<Text style={[
styles.compactUsername,
{
color: theme.colors.highEmphasis,
fontSize: isTV ? 18 : isLargeTablet ? 17 : isTablet ? 16 : 16
}
]}>
{username}
</Text>
{user.vip && (
<View style={[
styles.miniVipBadge,
{
paddingHorizontal: isTV ? 6 : isLargeTablet ? 5 : isTablet ? 4 : 4,
paddingVertical: isTV ? 2 : isLargeTablet ? 2 : isTablet ? 1 : 1,
borderRadius: isTV ? 8 : isLargeTablet ? 7 : isTablet ? 6 : 6
}
]}>
<Text style={[
styles.miniVipText,
{
fontSize: isTV ? 11 : isLargeTablet ? 10 : isTablet ? 9 : 9
}
]}>VIP</Text>
</View>
)}
{/* Trakt Icon - Top Right Corner */}
<View style={styles.traktIconContainer}>
<TraktIcon width={isTV ? 20 : isLargeTablet ? 18 : isTablet ? 16 : 16} height={isTV ? 20 : isLargeTablet ? 18 : isTablet ? 16 : 16} />
</View>
</View>
{/* Rating - Show stars */}
{comment.user_stats?.rating && (
{/* Header Section - Fixed at top */}
<View style={[
styles.compactRating,
styles.compactHeader,
{
marginBottom: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8
}
]}>
{renderCompactStars(comment.user_stats.rating)}
<Text style={[
styles.compactRatingText,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 14
<View style={styles.usernameContainer}>
<Text style={[
styles.compactUsername,
{
color: theme.colors.highEmphasis,
fontSize: isTV ? 18 : isLargeTablet ? 17 : isTablet ? 16 : 16
}
]}>
{username}
</Text>
{user.vip && (
<View style={[
styles.miniVipBadge,
{
paddingHorizontal: isTV ? 6 : isLargeTablet ? 5 : isTablet ? 4 : 4,
paddingVertical: isTV ? 2 : isLargeTablet ? 2 : isTablet ? 1 : 1,
borderRadius: isTV ? 8 : isLargeTablet ? 7 : isTablet ? 6 : 6
}
]}>
<Text style={[
styles.miniVipText,
{
fontSize: isTV ? 11 : isLargeTablet ? 10 : isTablet ? 9 : 9
}
]}>VIP</Text>
</View>
)}
</View>
</View>
{/* Rating - Show stars */}
{comment.user_stats?.rating && (
<View style={[
styles.compactRating,
{
marginBottom: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8
}
]}>
{comment.user_stats.rating}/10
</Text>
</View>
)}
{renderCompactStars(comment.user_stats.rating)}
<Text style={[
styles.compactRatingText,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 14
}
]}>
{comment.user_stats.rating}/10
</Text>
</View>
)}
{/* Comment Preview - Flexible area that fills space */}
<View style={[
styles.commentContainer,
shouldBlurContent ? styles.blurredContent : undefined,
{
marginBottom: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8
}
]}>
{shouldBlurContent ? (
<Text style={[
styles.compactComment,
{
color: theme.colors.highEmphasis,
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 14,
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 18 : 18
}
]}> This comment contains spoilers. Tap to reveal.</Text>
) : (
<MarkdownText
text={comment.comment}
theme={theme}
numberOfLines={isLargeScreen ? 4 : 3}
revealedInlineSpoilers={isSpoilerRevealed}
onSpoilerPress={onSpoilerPress}
textStyle={[
{/* Comment Preview - Flexible area that fills space */}
<View style={[
styles.commentContainer,
shouldBlurContent ? styles.blurredContent : undefined,
{
marginBottom: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8
}
]}>
{shouldBlurContent ? (
<Text style={[
styles.compactComment,
{
color: theme.colors.highEmphasis,
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 14,
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 18 : 18
}
]}
/>
)}
</View>
]}> This comment contains spoilers. Tap to reveal.</Text>
) : (
<MarkdownText
text={comment.comment}
theme={theme}
numberOfLines={isLargeScreen ? 4 : 3}
revealedInlineSpoilers={isSpoilerRevealed}
onSpoilerPress={onSpoilerPress}
textStyle={[
styles.compactComment,
{
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 14,
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 18 : 18
}
]}
/>
)}
</View>
{/* Meta Info - Fixed at bottom */}
<View style={[
styles.compactMeta,
{
paddingTop: isTV ? 8 : isLargeTablet ? 6 : isTablet ? 6 : 6
}
]}>
<View style={styles.compactBadges}>
{comment.spoiler && (
{/* Meta Info - Fixed at bottom */}
<View style={[
styles.compactMeta,
{
paddingTop: isTV ? 8 : isLargeTablet ? 6 : isTablet ? 6 : 6
}
]}>
<View style={styles.compactBadges}>
{comment.spoiler && (
<Text style={[
styles.spoilerMiniText,
{
color: theme.colors.error,
fontSize: isTV ? 13 : isLargeTablet ? 12 : isTablet ? 11 : 11
}
]}>Spoiler</Text>
)}
</View>
<View style={styles.compactStats}>
<Text style={[
styles.spoilerMiniText,
{
color: theme.colors.error,
styles.compactTime,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 13 : isLargeTablet ? 12 : isTablet ? 11 : 11
}
]}>Spoiler</Text>
)}
</View>
<View style={styles.compactStats}>
<Text style={[
styles.compactTime,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 13 : isLargeTablet ? 12 : isTablet ? 11 : 11
}
]}>
{formatRelativeTime(comment.created_at)}
</Text>
{comment.likes > 0 && (
<Text style={[
styles.compactStat,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 13 : isLargeTablet ? 12 : isTablet ? 12 : 12
}
]}>
👍 {comment.likes}
{formatRelativeTime(comment.created_at)}
</Text>
)}
{comment.replies > 0 && (
<Text style={[
styles.compactStat,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 13 : isLargeTablet ? 12 : isTablet ? 12 : 12
}
]}>
💬 {comment.replies}
</Text>
)}
{comment.likes > 0 && (
<Text style={[
styles.compactStat,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 13 : isLargeTablet ? 12 : isTablet ? 12 : 12
}
]}>
👍 {comment.likes}
</Text>
)}
{comment.replies > 0 && (
<Text style={[
styles.compactStat,
{
color: theme.colors.mediumEmphasis,
fontSize: isTV ? 13 : isLargeTablet ? 12 : isTablet ? 12 : 12
}
]}>
💬 {comment.replies}
</Text>
)}
</View>
</View>
</View>
</TouchableOpacity>
</Animated.View>
);
@ -614,105 +614,105 @@ const ExpandedCommentBottomSheet: React.FC<{
nestedScrollEnabled
keyboardShouldPersistTaps="handled"
>
{/* Close Button */}
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
<MaterialIcons name="close" size={24} color={theme.colors.highEmphasis} />
</TouchableOpacity>
{/* Close Button */}
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
<MaterialIcons name="close" size={24} color={theme.colors.highEmphasis} />
</TouchableOpacity>
{/* User Info */}
<View style={styles.modalHeader}>
<View style={styles.userInfo}>
<Text
style={[styles.modalUsername, { color: theme.colors.highEmphasis }]}
numberOfLines={1}
ellipsizeMode="tail"
>
{username}
</Text>
{user.vip && (
<View style={styles.vipBadge}>
<Text style={styles.vipText}>VIP</Text>
</View>
)}
</View>
{(() => {
const { datePart, timePart } = formatDateParts(comment.created_at);
return (
<View style={styles.dateTimeContainer}>
<Text style={[styles.modalDate, { color: theme.colors.mediumEmphasis }]}>
{datePart}
</Text>
{!!timePart && (
<Text style={[styles.modalTime, { color: theme.colors.mediumEmphasis }]}>
{timePart}
</Text>
)}
</View>
);
})()}
</View>
{/* Rating */}
{comment.user_stats?.rating && (
<View style={styles.modalRating}>
{renderStars(comment.user_stats.rating)}
<Text style={[styles.modalRatingText, { color: theme.colors.mediumEmphasis }]}>
{comment.user_stats.rating}/10
</Text>
</View>
)}
{/* Full Comment (Markdown with inline spoilers) */}
{shouldBlurModalContent ? (
<View style={styles.spoilerContainer}>
<View style={[styles.spoilerIcon, { backgroundColor: theme.colors.card }]}>
<MaterialIcons name="visibility-off" size={20} color={theme.colors.mediumEmphasis} />
{/* User Info */}
<View style={styles.modalHeader}>
<View style={styles.userInfo}>
<Text
style={[styles.modalUsername, { color: theme.colors.highEmphasis }]}
numberOfLines={1}
ellipsizeMode="tail"
>
{username}
</Text>
{user.vip && (
<View style={styles.vipBadge}>
<Text style={styles.vipText}>VIP</Text>
</View>
<Text style={[styles.spoilerTitle, { color: theme.colors.highEmphasis }]}>Contains spoilers</Text>
<TouchableOpacity
style={[styles.revealButton, { borderColor: theme.colors.primary }]}
onPress={onSpoilerPress}
activeOpacity={0.9}
>
<MaterialIcons name="visibility" size={18} color={theme.colors.primary} />
<Text style={[styles.revealButtonText, { color: theme.colors.primary }]}>Reveal</Text>
</TouchableOpacity>
</View>
) : (
<View style={{ marginBottom: 16 }}>
<MarkdownText
text={comment.comment}
theme={theme}
revealedInlineSpoilers={true}
textStyle={styles.modalComment}
/>
</View>
)}
{/* Comment Meta */}
<View style={styles.modalMeta}>
{comment.spoiler && (
<Text style={[styles.spoilerText, { color: theme.colors.error }]}>Spoiler</Text>
)}
<View style={styles.modalStats}>
{comment.likes > 0 && (
<View style={styles.likesContainer}>
<MaterialIcons name="thumb-up" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.likesText, { color: theme.colors.mediumEmphasis }]}>
{comment.likes}
</Text>
</View>
)}
{comment.replies > 0 && (
<View style={styles.repliesContainer}>
<MaterialIcons name="chat-bubble-outline" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.repliesText, { color: theme.colors.mediumEmphasis }]}>
{comment.replies}
</Text>
</View>
)}
</View>
</View>
{(() => {
const { datePart, timePart } = formatDateParts(comment.created_at);
return (
<View style={styles.dateTimeContainer}>
<Text style={[styles.modalDate, { color: theme.colors.mediumEmphasis }]}>
{datePart}
</Text>
{!!timePart && (
<Text style={[styles.modalTime, { color: theme.colors.mediumEmphasis }]}>
{timePart}
</Text>
)}
</View>
);
})()}
</View>
{/* Rating */}
{comment.user_stats?.rating && (
<View style={styles.modalRating}>
{renderStars(comment.user_stats.rating)}
<Text style={[styles.modalRatingText, { color: theme.colors.mediumEmphasis }]}>
{comment.user_stats.rating}/10
</Text>
</View>
)}
{/* Full Comment (Markdown with inline spoilers) */}
{shouldBlurModalContent ? (
<View style={styles.spoilerContainer}>
<View style={[styles.spoilerIcon, { backgroundColor: theme.colors.card }]}>
<MaterialIcons name="visibility-off" size={20} color={theme.colors.mediumEmphasis} />
</View>
<Text style={[styles.spoilerTitle, { color: theme.colors.highEmphasis }]}>Contains spoilers</Text>
<TouchableOpacity
style={[styles.revealButton, { borderColor: theme.colors.primary }]}
onPress={onSpoilerPress}
activeOpacity={0.9}
>
<MaterialIcons name="visibility" size={18} color={theme.colors.primary} />
<Text style={[styles.revealButtonText, { color: theme.colors.primary }]}>Reveal</Text>
</TouchableOpacity>
</View>
) : (
<View style={{ marginBottom: 16 }}>
<MarkdownText
text={comment.comment}
theme={theme}
revealedInlineSpoilers={true}
textStyle={styles.modalComment}
/>
</View>
)}
{/* Comment Meta */}
<View style={styles.modalMeta}>
{comment.spoiler && (
<Text style={[styles.spoilerText, { color: theme.colors.error }]}>Spoiler</Text>
)}
<View style={styles.modalStats}>
{comment.likes > 0 && (
<View style={styles.likesContainer}>
<MaterialIcons name="thumb-up" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.likesText, { color: theme.colors.mediumEmphasis }]}>
{comment.likes}
</Text>
</View>
)}
{comment.replies > 0 && (
<View style={styles.repliesContainer}>
<MaterialIcons name="chat-bubble-outline" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.repliesText, { color: theme.colors.mediumEmphasis }]}>
{comment.replies}
</Text>
</View>
)}
</View>
</View>
</BottomSheetScrollView>
</BottomSheet>
);
@ -732,7 +732,7 @@ export const CommentsSection: React.FC<CommentsSectionProps> = ({
// Enhanced responsive sizing for tablets and TV screens
const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('window').height;
// Determine device type based on width
const getDeviceType = useCallback(() => {
if (deviceWidth >= BREAKPOINTS.tv) return 'tv';
@ -740,13 +740,13 @@ export const CommentsSection: React.FC<CommentsSectionProps> = ({
if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet';
return 'phone';
}, [deviceWidth]);
const deviceType = getDeviceType();
const isTablet = deviceType === 'tablet';
const isLargeTablet = deviceType === 'largeTablet';
const isTV = deviceType === 'tv';
const isLargeScreen = isTablet || isLargeTablet || isTV;
// Enhanced spacing and padding
const horizontalPadding = useMemo(() => {
switch (deviceType) {
@ -772,7 +772,7 @@ export const CommentsSection: React.FC<CommentsSectionProps> = ({
} = useTraktComments({
imdbId,
type: type === 'show' ? (season !== undefined && episode !== undefined ? 'episode' :
season !== undefined ? 'season' : 'show') : 'movie',
season !== undefined ? 'season' : 'show') : 'movie',
season,
episode,
enabled: true,
@ -924,8 +924,8 @@ export const CommentsSection: React.FC<CommentsSectionProps> = ({
}
]}>
<Text style={[
styles.title,
{
styles.title,
{
color: currentTheme.colors.highEmphasis,
fontSize: isTV ? 28 : isLargeTablet ? 26 : isTablet ? 24 : 20
}
@ -992,7 +992,7 @@ export const CommentsSection: React.FC<CommentsSectionProps> = ({
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
) : (
<>
<Text style={[styles.loadMoreText, { color: currentTheme.colors.primary }]}>
<Text style={[styles.loadMoreText, { color: currentTheme.colors.primary }]}>
Load More
</Text>
<MaterialIcons name="chevron-right" size={20} color={currentTheme.colors.primary} />
@ -1022,15 +1022,19 @@ export const CommentBottomSheet: React.FC<{
}> = ({ comment, visible, onClose, theme, isSpoilerRevealed, onSpoilerPress }) => {
const bottomSheetRef = useRef<BottomSheet>(null);
// Early return before any Reanimated components are rendered
// This prevents the BottomSheet from initializing when not needed
if (!visible || !comment) {
return null;
}
console.log('CommentBottomSheet: Rendered with visible:', visible, 'comment:', comment?.id);
// Calculate the index based on visibility - start at medium height (50%)
const sheetIndex = visible && comment ? 1 : -1;
const sheetIndex = 1; // Always 1 when visible and comment are truthy
console.log('CommentBottomSheet: Calculated sheetIndex:', sheetIndex);
if (!comment) return null;
const user = comment.user || {};
const username = user.name || user.username || 'Anonymous User';
const hasSpoiler = comment.spoiler;
@ -1115,100 +1119,100 @@ export const CommentBottomSheet: React.FC<{
nestedScrollEnabled
keyboardShouldPersistTaps="handled"
>
{/* User Info */}
<View style={styles.modalHeader}>
<View style={styles.userInfo}>
<Text
style={[styles.modalUsername, { color: theme.colors.highEmphasis }]}
numberOfLines={1}
ellipsizeMode="tail"
>
{username}
</Text>
{user.vip && (
<View style={styles.vipBadge}>
<Text style={styles.vipText}>VIP</Text>
</View>
)}
</View>
{(() => {
const { datePart, timePart } = formatDateParts(comment.created_at);
return (
<View style={styles.dateTimeContainer}>
<Text style={[styles.modalDate, { color: theme.colors.mediumEmphasis }]}>
{datePart}
</Text>
{!!timePart && (
<Text style={[styles.modalTime, { color: theme.colors.mediumEmphasis }]}>
{timePart}
</Text>
)}
</View>
);
})()}
</View>
{/* Rating */}
{comment.user_stats?.rating && (
<View style={styles.modalRating}>
{renderStars(comment.user_stats.rating)}
<Text style={[styles.modalRatingText, { color: theme.colors.mediumEmphasis }]}>
{comment.user_stats.rating}/10
</Text>
</View>
)}
{/* Full Comment (Markdown with inline spoilers) */}
{shouldBlurModalContent ? (
<View style={styles.spoilerContainer}>
<View style={[styles.spoilerIcon, { backgroundColor: theme.colors.card }]}>
<MaterialIcons name="visibility-off" size={20} color={theme.colors.mediumEmphasis} />
{/* User Info */}
<View style={styles.modalHeader}>
<View style={styles.userInfo}>
<Text
style={[styles.modalUsername, { color: theme.colors.highEmphasis }]}
numberOfLines={1}
ellipsizeMode="tail"
>
{username}
</Text>
{user.vip && (
<View style={styles.vipBadge}>
<Text style={styles.vipText}>VIP</Text>
</View>
<Text style={[styles.spoilerTitle, { color: theme.colors.highEmphasis }]}>Contains spoilers</Text>
<TouchableOpacity
style={[styles.revealButton, { borderColor: theme.colors.primary }]}
onPress={onSpoilerPress}
activeOpacity={0.9}
>
<MaterialIcons name="visibility" size={18} color={theme.colors.primary} />
<Text style={[styles.revealButtonText, { color: theme.colors.primary }]}>Reveal</Text>
</TouchableOpacity>
</View>
) : (
<View style={{ marginBottom: 16 }}>
<MarkdownText
text={comment.comment}
theme={theme}
revealedInlineSpoilers={true}
textStyle={styles.modalComment}
/>
</View>
)}
{/* Comment Meta */}
<View style={styles.modalMeta}>
{comment.spoiler && (
<Text style={[styles.spoilerText, { color: theme.colors.error }]}>Spoiler</Text>
)}
<View style={styles.modalStats}>
{comment.likes > 0 && (
<View style={styles.likesContainer}>
<MaterialIcons name="thumb-up" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.likesText, { color: theme.colors.mediumEmphasis }]}>
{comment.likes}
</Text>
</View>
)}
{comment.replies > 0 && (
<View style={styles.repliesContainer}>
<MaterialIcons name="chat-bubble-outline" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.repliesText, { color: theme.colors.mediumEmphasis }]}>
{comment.replies}
</Text>
</View>
)}
</View>
</View>
{(() => {
const { datePart, timePart } = formatDateParts(comment.created_at);
return (
<View style={styles.dateTimeContainer}>
<Text style={[styles.modalDate, { color: theme.colors.mediumEmphasis }]}>
{datePart}
</Text>
{!!timePart && (
<Text style={[styles.modalTime, { color: theme.colors.mediumEmphasis }]}>
{timePart}
</Text>
)}
</View>
);
})()}
</View>
{/* Rating */}
{comment.user_stats?.rating && (
<View style={styles.modalRating}>
{renderStars(comment.user_stats.rating)}
<Text style={[styles.modalRatingText, { color: theme.colors.mediumEmphasis }]}>
{comment.user_stats.rating}/10
</Text>
</View>
)}
{/* Full Comment (Markdown with inline spoilers) */}
{shouldBlurModalContent ? (
<View style={styles.spoilerContainer}>
<View style={[styles.spoilerIcon, { backgroundColor: theme.colors.card }]}>
<MaterialIcons name="visibility-off" size={20} color={theme.colors.mediumEmphasis} />
</View>
<Text style={[styles.spoilerTitle, { color: theme.colors.highEmphasis }]}>Contains spoilers</Text>
<TouchableOpacity
style={[styles.revealButton, { borderColor: theme.colors.primary }]}
onPress={onSpoilerPress}
activeOpacity={0.9}
>
<MaterialIcons name="visibility" size={18} color={theme.colors.primary} />
<Text style={[styles.revealButtonText, { color: theme.colors.primary }]}>Reveal</Text>
</TouchableOpacity>
</View>
) : (
<View style={{ marginBottom: 16 }}>
<MarkdownText
text={comment.comment}
theme={theme}
revealedInlineSpoilers={true}
textStyle={styles.modalComment}
/>
</View>
)}
{/* Comment Meta */}
<View style={styles.modalMeta}>
{comment.spoiler && (
<Text style={[styles.spoilerText, { color: theme.colors.error }]}>Spoiler</Text>
)}
<View style={styles.modalStats}>
{comment.likes > 0 && (
<View style={styles.likesContainer}>
<MaterialIcons name="thumb-up" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.likesText, { color: theme.colors.mediumEmphasis }]}>
{comment.likes}
</Text>
</View>
)}
{comment.replies > 0 && (
<View style={styles.repliesContainer}>
<MaterialIcons name="chat-bubble-outline" size={16} color={theme.colors.mediumEmphasis} />
<Text style={[styles.repliesText, { color: theme.colors.mediumEmphasis }]}>
{comment.replies}
</Text>
</View>
)}
</View>
</View>
</BottomSheetScrollView>
</BottomSheet>
);

View file

@ -3124,7 +3124,7 @@ const AndroidVideoPlayer: React.FC = () => {
}
]}
>
{/* Combined gesture handler for left side - brightness + tap + long press */}
{/* Left side gesture handler - tap + long press (brightness gesture disabled) */}
<LongPressGestureHandler
onActivated={onLongPressActivated}
onEnded={onLongPressEnd}
@ -3133,29 +3133,20 @@ const AndroidVideoPlayer: React.FC = () => {
shouldCancelWhenOutside={false}
simultaneousHandlers={[]}
>
<PanGestureHandler
onGestureEvent={gestureControls.onBrightnessGestureEvent}
activeOffsetY={[-10, 10]}
failOffsetX={[-30, 30]}
<TapGestureHandler
onActivated={toggleControls}
shouldCancelWhenOutside={false}
simultaneousHandlers={[]}
maxPointers={1}
>
<TapGestureHandler
onActivated={toggleControls}
shouldCancelWhenOutside={false}
simultaneousHandlers={[]}
>
<View style={{
position: 'absolute',
top: screenDimensions.height * 0.15, // Back to original margin
left: 0,
width: screenDimensions.width * 0.4, // Back to larger area (40% of screen)
height: screenDimensions.height * 0.7, // Back to larger middle portion (70% of screen)
zIndex: 10, // Higher z-index to capture gestures
}} />
</TapGestureHandler>
</PanGestureHandler>
<View style={{
position: 'absolute',
top: screenDimensions.height * 0.15,
left: 0,
width: screenDimensions.width * 0.4,
height: screenDimensions.height * 0.7,
zIndex: 10,
}} />
</TapGestureHandler>
</LongPressGestureHandler>
{/* Combined gesture handler for right side - volume + tap + long press */}

View file

@ -765,7 +765,7 @@ const HomeScreen = () => {
);
case 'loadMore':
return (
<Animated.View entering={FadeIn.duration(300)}>
<View>
<View style={styles.loadMoreContainer}>
<TouchableOpacity
style={[styles.loadMoreButton, { backgroundColor: currentTheme.colors.primary }]}
@ -777,7 +777,7 @@ const HomeScreen = () => {
</Text>
</TouchableOpacity>
</View>
</Animated.View>
</View>
);
case 'welcome':
return <FirstTimeWelcome />;

View file

@ -41,7 +41,11 @@ import Animated, {
Easing,
interpolateColor,
withSpring,
createAnimatedComponent,
} from 'react-native-reanimated';
// Create animated version of SafeAreaView for use with Reanimated styles
const AnimatedSafeAreaView = createAnimatedComponent(SafeAreaView);
import { RouteProp } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { RootStackParamList } from '../navigation/AppNavigator';
@ -911,7 +915,7 @@ const MetadataScreen: React.FC = () => {
return (
<Animated.View style={[animatedBackgroundStyle, { flex: 1 }]}>
<SafeAreaView
<AnimatedSafeAreaView
style={[containerStyle, styles.container]}
edges={[]}
>
@ -1417,7 +1421,7 @@ const MetadataScreen: React.FC = () => {
isSpoilerRevealed={selectedComment ? revealedSpoilers.has(selectedComment.id.toString()) : false}
onSpoilerPress={() => selectedComment && handleSpoilerPress(selectedComment)}
/>
</SafeAreaView>
</AnimatedSafeAreaView>
</Animated.View>
);
};

View file

@ -18,7 +18,7 @@ import {
Platform,
Easing,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { MaterialIcons, Feather } from '@expo/vector-icons';
import { catalogService, StreamingContent, GroupedSearchResults, AddonSearchResults } from '../services/catalogService';
@ -235,6 +235,15 @@ const SearchScreen = () => {
const addonOrderRankRef = useRef<Record<string, number>>({});
// Track if this is the initial mount to prevent unnecessary operations
const isInitialMount = useRef(true);
// Track mount status for async operations
const isMounted = useRef(true);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
// DropUpMenu state
const [menuVisible, setMenuVisible] = useState(false);
const [selectedItem, setSelectedItem] = useState<StreamingContent | null>(null);
@ -380,45 +389,87 @@ const SearchScreen = () => {
// Create a stable debounced search function using useMemo
const debouncedSearch = useMemo(() => {
return debounce(async (searchQuery: string) => {
if (!searchQuery.trim()) {
// Cancel any in-flight live search
liveSearchHandle.current?.cancel();
liveSearchHandle.current = null;
setResults({ byAddon: [], allResults: [] });
setSearching(false);
return;
// Cancel any in-flight live search
liveSearchHandle.current?.cancel();
liveSearchHandle.current = null;
performLiveSearch(searchQuery);
}, 800);
}, []); // Empty dependency array - create once and never recreate
// Track focus state to strictly prevent updates when blurred (fixes Telemetry crash)
useFocusEffect(
useCallback(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
// Cancel any active searches immediately on blur
if (liveSearchHandle.current) {
liveSearchHandle.current.cancel();
liveSearchHandle.current = null;
}
debouncedSearch.cancel();
};
}, [debouncedSearch])
);
// Live search implementation
const performLiveSearch = async (searchQuery: string) => {
// strict guard: don't search if unmounted or blurred
if (!isMounted.current) return;
if (!searchQuery || searchQuery.trim().length === 0) {
setResults({ byAddon: [], allResults: [] });
setSearching(false);
return;
}
setSearching(true);
setResults({ byAddon: [], allResults: [] });
// Reset order rank for new search
addonOrderRankRef.current = {};
try {
if (liveSearchHandle.current) {
liveSearchHandle.current.cancel();
}
// Cancel prior live search
liveSearchHandle.current?.cancel();
setResults({ byAddon: [], allResults: [] });
setSearching(true);
// Pre-fetch addon list to establish a stable order rank
const addons = await catalogService.getAllAddons();
// ... (rank logic) ...
const rank: Record<string, number> = {};
let rankCounter = 0;
logger.info('Starting live search for:', searchQuery);
// Preload addon order to keep sections sorted by installation order
try {
const addons = await catalogService.getAllAddons();
const rank: Record<string, number> = {};
addons.forEach((a, idx) => { rank[a.id] = idx; });
addonOrderRankRef.current = rank;
} catch { }
// Cinemeta first
rank['com.linvo.cinemeta'] = rankCounter++;
// Then others
addons.forEach(addon => {
if (addon.id !== 'com.linvo.cinemeta') {
rank[addon.id] = rankCounter++;
}
});
addonOrderRankRef.current = rank;
const handle = catalogService.startLiveSearch(searchQuery, async (section: AddonSearchResults) => {
// Append/update this addon section immediately with minimal changes
setResults(prev => {
const rank = addonOrderRankRef.current;
const getRank = (id: string) => rank[id] ?? Number.MAX_SAFE_INTEGER;
// Prevent updates if component is unmounted or blurred
if (!isMounted.current) return;
// Append/update this addon section...
setResults(prev => {
// ... (existing update logic) ...
if (!isMounted.current) return prev; // Extra guard inside setter
const getRank = (id: string) => addonOrderRankRef.current[id] ?? Number.MAX_SAFE_INTEGER;
// ... (same logic as before) ...
const existingIndex = prev.byAddon.findIndex(s => s.addonId === section.addonId);
if (existingIndex >= 0) {
// Update existing section in-place (preserve order and other sections)
const copy = prev.byAddon.slice();
copy[existingIndex] = section;
return { byAddon: copy, allResults: prev.allResults };
}
// Insert new section at correct position based on rank
// Insert new section
const insertRank = getRank(section.addonId);
let insertAt = prev.byAddon.length;
for (let i = 0; i < prev.byAddon.length; i++) {
@ -442,15 +493,24 @@ const SearchScreen = () => {
return { byAddon: nextByAddon, allResults: prev.allResults };
});
// Save to recents after first result batch
try {
await saveRecentSearch(searchQuery);
} catch { }
});
liveSearchHandle.current = handle;
}, 800);
}, []); // Empty dependency array - create once and never recreate
liveSearchHandle.current = handle;
await handle.done;
if (isMounted.current) {
setSearching(false);
}
} catch (error) {
if (isMounted.current) {
console.error('Live search error:', error);
setSearching(false);
}
}
};
useEffect(() => {
// Skip initial mount to prevent unnecessary operations
if (isInitialMount.current) {
@ -503,22 +563,20 @@ const SearchScreen = () => {
if (!showRecent || recentSearches.length === 0) return null;
return (
<Animated.View
<View
style={styles.recentSearchesContainer}
entering={FadeIn.duration(300)}
>
<Text style={[styles.carouselTitle, { color: currentTheme.colors.white }]}>
Recent Searches
</Text>
{recentSearches.map((search, index) => (
<AnimatedTouchable
<TouchableOpacity
key={index}
style={styles.recentSearchItem}
onPress={() => {
setQuery(search);
Keyboard.dismiss();
}}
entering={FadeIn.duration(300).delay(index * 50)}
>
<MaterialIcons
name="history"
@ -541,9 +599,9 @@ const SearchScreen = () => {
>
<MaterialIcons name="close" size={16} color={currentTheme.colors.lightGray} />
</TouchableOpacity>
</AnimatedTouchable>
</TouchableOpacity>
))}
</Animated.View>
</View>
);
};
@ -573,7 +631,7 @@ const SearchScreen = () => {
return () => unsubscribe();
}, [item.id, item.type]);
return (
<AnimatedTouchable
<TouchableOpacity
style={styles.horizontalItem}
onPress={() => {
navigation.navigate('Metadata', { id: item.id, type: item.type });
@ -584,7 +642,6 @@ const SearchScreen = () => {
// Do NOT toggle refreshFlag here
}}
delayLongPress={300}
entering={FadeIn.duration(300).delay(index * 50)}
activeOpacity={0.7}
>
<View style={[styles.horizontalItemPosterContainer, {
@ -634,7 +691,7 @@ const SearchScreen = () => {
{item.year}
</Text>
)}
</AnimatedTouchable>
</TouchableOpacity>
);
};
@ -664,7 +721,7 @@ const SearchScreen = () => {
);
return (
<Animated.View entering={FadeIn.duration(300).delay(addonIndex * 50)}>
<View>
{/* Addon Header */}
<View style={styles.addonHeaderContainer}>
<Text style={[styles.addonHeaderText, { color: currentTheme.colors.white }]}>
@ -679,7 +736,7 @@ const SearchScreen = () => {
{/* Movies */}
{movieResults.length > 0 && (
<Animated.View style={[styles.carouselContainer, { marginBottom: isTV ? 40 : isLargeTablet ? 36 : isTablet ? 32 : 24 }]} entering={FadeIn.duration(300)}>
<View style={[styles.carouselContainer, { marginBottom: isTV ? 40 : isLargeTablet ? 36 : isTablet ? 32 : 24 }]}>
<Text style={[
styles.carouselSubtitle,
{
@ -708,12 +765,12 @@ const SearchScreen = () => {
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.horizontalListContent}
/>
</Animated.View>
</View>
)}
{/* TV Shows */}
{seriesResults.length > 0 && (
<Animated.View style={[styles.carouselContainer, { marginBottom: isTV ? 40 : isLargeTablet ? 36 : isTablet ? 32 : 24 }]} entering={FadeIn.duration(300)}>
<View style={[styles.carouselContainer, { marginBottom: isTV ? 40 : isLargeTablet ? 36 : isTablet ? 32 : 24 }]}>
<Text style={[
styles.carouselSubtitle,
{
@ -742,12 +799,12 @@ const SearchScreen = () => {
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.horizontalListContent}
/>
</Animated.View>
</View>
)}
{/* Other types */}
{otherResults.length > 0 && (
<Animated.View style={[styles.carouselContainer, { marginBottom: isTV ? 40 : isLargeTablet ? 36 : isTablet ? 32 : 24 }]} entering={FadeIn.duration(300)}>
<View style={[styles.carouselContainer, { marginBottom: isTV ? 40 : isLargeTablet ? 36 : isTablet ? 32 : 24 }]}>
<Text style={[
styles.carouselSubtitle,
{
@ -776,9 +833,9 @@ const SearchScreen = () => {
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.horizontalListContent}
/>
</Animated.View>
</View>
)}
</Animated.View>
</View>
);
}, (prev, next) => {
// Only re-render if this section's reference changed
@ -804,13 +861,8 @@ const SearchScreen = () => {
}, []);
return (
<Animated.View
<View
style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}
entering={Platform.OS === 'android' ? undefined : FadeIn.duration(350)}
exiting={Platform.OS === 'android' ?
FadeOut.duration(200).withInitialValues({ opacity: 1 }) :
FadeOut.duration(250)
}
>
<StatusBar
barStyle="light-content"
@ -884,9 +936,8 @@ const SearchScreen = () => {
/>
</View>
) : query.trim().length === 1 ? (
<Animated.View
<View
style={styles.emptyContainer}
entering={FadeIn.duration(300)}
>
<MaterialIcons
name="search"
@ -899,11 +950,10 @@ const SearchScreen = () => {
<Text style={[styles.emptySubtext, { color: currentTheme.colors.lightGray }]}>
Type at least 2 characters to search
</Text>
</Animated.View>
</View>
) : searched && !hasResultsToShow ? (
<Animated.View
<View
style={styles.emptyContainer}
entering={FadeIn.duration(300)}
>
<MaterialIcons
name="search-off"
@ -916,14 +966,13 @@ const SearchScreen = () => {
<Text style={[styles.emptySubtext, { color: currentTheme.colors.lightGray }]}>
Try different keywords or check your spelling
</Text>
</Animated.View>
</View>
) : (
<Animated.ScrollView
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewContent}
keyboardShouldPersistTaps="handled"
onScrollBeginDrag={Keyboard.dismiss}
entering={FadeIn.duration(300)}
showsVerticalScrollIndicator={false}
>
{!query.trim() && renderRecentSearches()}
@ -935,7 +984,7 @@ const SearchScreen = () => {
addonIndex={addonIndex}
/>
))}
</Animated.ScrollView>
</ScrollView>
)}
</View>
{/* DropUpMenu integration for search results */}
@ -981,7 +1030,7 @@ const SearchScreen = () => {
}}
/>
)}
</Animated.View>
</View>
);
};