mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
crash fix test
This commit is contained in:
parent
063f8a8c1b
commit
703c3e3cfb
36 changed files with 1818 additions and 3248 deletions
|
|
@ -1,7 +1,11 @@
|
|||
apply plugin: "com.android.application"
|
||||
// @generated begin safeExtGet - expo prebuild (DO NOT MODIFY) sync-be4acad6508a6820102c74ab393bd7ab1093e6c0
|
||||
def safeExtGet(prop, fallback) {
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
// @generated end safeExtGet
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
apply plugin: "com.facebook.react"
|
||||
apply plugin: "io.sentry.android.gradle"
|
||||
|
||||
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
||||
|
||||
|
|
@ -100,36 +104,6 @@ android {
|
|||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
|
||||
// Split APKs by architecture only for smaller downloads
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
universalApk true
|
||||
}
|
||||
density {
|
||||
enable false
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unique version codes for each split APK
|
||||
def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
def baseVersionCode = 29 // Current versionCode 29 from defaultConfig
|
||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
|
||||
def versionCode = baseVersionCode * 100 // Base multiplier
|
||||
|
||||
if (abiName != null) {
|
||||
versionCode += abiVersionCodes.get(abiName)
|
||||
}
|
||||
|
||||
output.versionCodeOverride = versionCode
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
|
|
@ -185,34 +159,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
sentry {
|
||||
// Enables or disables the automatic configuration of Native Symbols
|
||||
// for Sentry. This executes sentry-cli automatically so
|
||||
// you don't need to do it manually.
|
||||
// Default is disabled.
|
||||
uploadNativeSymbols = true
|
||||
|
||||
// Enables or disables the automatic upload of the app's native source code to Sentry.
|
||||
// This executes sentry-cli with the --include-sources param automatically so
|
||||
// you don't need to do it manually.
|
||||
// This option has an effect only when [uploadNativeSymbols] is enabled.
|
||||
// Default is disabled.
|
||||
includeNativeSources = true
|
||||
|
||||
// `@sentry/react-native` ships with compatible `sentry-android`
|
||||
// This option would install the latest version that ships with the SDK or SAGP (Sentry Android Gradle Plugin)
|
||||
// which might be incompatible with the React Native SDK
|
||||
// Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations).
|
||||
// Default is enabled.
|
||||
autoInstallation {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'com.caverock', module: 'androidsvg'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// @generated begin react-native-google-cast-dependencies - expo prebuild (DO NOT MODIFY) sync-3822a3c86222e7aca74039b551612aab7e75365d
|
||||
implementation "com.google.android.gms:play-services-cast-framework:${safeExtGet('castFrameworkVersion', '+')}"
|
||||
|
|
@ -243,17 +189,4 @@ dependencies {
|
|||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
||||
// Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3
|
||||
implementation files("libs/lib-decoder-ffmpeg-release.aar")
|
||||
|
||||
// MPV Player library
|
||||
implementation files("libs/libmpv-release.aar")
|
||||
|
||||
// Google Cast Framework
|
||||
implementation "com.google.android.gms:play-services-cast-framework:${safeExtGet('castFrameworkVersion', '+')}"
|
||||
}
|
||||
|
||||
def safeExtGet(prop, fallback) {
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
14
android/app/proguard-rules.pro
vendored
14
android/app/proguard-rules.pro
vendored
|
|
@ -12,17 +12,3 @@
|
|||
-keep class com.facebook.react.turbomodule.** { *; }
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# Media3 / ExoPlayer keep (extensions and reflection)
|
||||
-keep class androidx.media3.** { *; }
|
||||
-dontwarn androidx.media3.**
|
||||
|
||||
# FastImage / Glide ProGuard rules
|
||||
-keep public class com.dylanvann.fastimage.* {*;}
|
||||
-keep public class com.dylanvann.fastimage.** {*;}
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-sdk tools:overrideLibrary="dev.jdtech.mpv"/>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
|
@ -8,6 +6,9 @@
|
|||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.faketouch" android:required="false"/>
|
||||
<uses-feature android:name="android.software.leanback" android:required="false"/>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
|
@ -23,10 +24,11 @@
|
|||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ERROR_RECOVERY_ONLY"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="30000"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"/>
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="unspecified">
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
// @generated begin react-native-google-cast-version-import - expo prebuild (DO NOT MODIFY) sync-751dea5919495636a44001495c681e3442af2777
|
||||
ext {
|
||||
buildToolsVersion = "35.0.0"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 35
|
||||
targetSdkVersion = 35
|
||||
castFrameworkVersion = "22.1.0"
|
||||
castFrameworkVersion = "+"
|
||||
}
|
||||
// @generated end react-native-google-cast-version-import
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
|
@ -16,7 +14,6 @@ buildscript {
|
|||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
classpath("io.sentry:sentry-android-gradle-plugin:5.12.2")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
|
|
|
|||
6
app.json
6
app.json
|
|
@ -37,7 +37,8 @@
|
|||
},
|
||||
"bundleIdentifier": "com.nuvio.app",
|
||||
"associatedDomains": [],
|
||||
"jsEngine": "hermes"
|
||||
"jsEngine": "hermes",
|
||||
"appleTeamId": "8QBDZ766S3"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
|
|
@ -67,6 +68,7 @@
|
|||
},
|
||||
"owner": "nayifleo",
|
||||
"plugins": [
|
||||
"@react-native-tvos/config-tv",
|
||||
[
|
||||
"@sentry/react-native/expo",
|
||||
{
|
||||
|
|
@ -100,4 +102,4 @@
|
|||
},
|
||||
"runtimeVersion": "1.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
eas.json
26
eas.json
|
|
@ -8,9 +8,21 @@
|
|||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"development_tv": {
|
||||
"extends": "development",
|
||||
"env": {
|
||||
"EXPO_TV": "1"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"preview_tv": {
|
||||
"extends": "preview",
|
||||
"env": {
|
||||
"EXPO_TV": "1"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": true,
|
||||
"extends": "apk",
|
||||
|
|
@ -20,12 +32,24 @@
|
|||
"image": "latest"
|
||||
}
|
||||
},
|
||||
"production_tv": {
|
||||
"extends": "production",
|
||||
"env": {
|
||||
"EXPO_TV": "1"
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"distribution": "store",
|
||||
"android": {
|
||||
"buildType": "app-bundle"
|
||||
}
|
||||
},
|
||||
"release_tv": {
|
||||
"extends": "release",
|
||||
"env": {
|
||||
"EXPO_TV": "1"
|
||||
}
|
||||
},
|
||||
"apk": {
|
||||
"android": {
|
||||
"buildType": "apk",
|
||||
|
|
@ -36,4 +60,4 @@
|
|||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
//
|
||||
// KSPlayerManager.m
|
||||
// Nuvio
|
||||
//
|
||||
// Created by KSPlayer integration
|
||||
//
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
@interface RCT_EXTERN_MODULE(KSPlayerViewManager, RCTViewManager)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
|
||||
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(volume, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(rate, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(audioTrack, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(textTrack, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(usesExternalPlaybackWhileExternalScreenIsActive, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleBottomOffset, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleFontSize, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString)
|
||||
|
||||
// Event properties
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onBuffering, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onEnd, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onBufferingProgress, RCTDirectEventBlock)
|
||||
|
||||
RCT_EXTERN_METHOD(seek:(nonnull NSNumber *)node toTime:(nonnull NSNumber *)time)
|
||||
RCT_EXTERN_METHOD(setSource:(nonnull NSNumber *)node source:(nonnull NSDictionary *)source)
|
||||
RCT_EXTERN_METHOD(setPaused:(nonnull NSNumber *)node paused:(BOOL)paused)
|
||||
RCT_EXTERN_METHOD(setVolume:(nonnull NSNumber *)node volume:(nonnull NSNumber *)volume)
|
||||
RCT_EXTERN_METHOD(setPlaybackRate:(nonnull NSNumber *)node rate:(nonnull NSNumber *)rate)
|
||||
RCT_EXTERN_METHOD(setAudioTrack:(nonnull NSNumber *)node trackId:(nonnull NSNumber *)trackId)
|
||||
RCT_EXTERN_METHOD(setTextTrack:(nonnull NSNumber *)node trackId:(nonnull NSNumber *)trackId)
|
||||
RCT_EXTERN_METHOD(getTracks:(nonnull NSNumber *)node resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(setAllowsExternalPlayback:(nonnull NSNumber *)node allows:(BOOL)allows)
|
||||
RCT_EXTERN_METHOD(setUsesExternalPlaybackWhileExternalScreenIsActive:(nonnull NSNumber *)node uses:(BOOL)uses)
|
||||
RCT_EXTERN_METHOD(getAirPlayState:(nonnull NSNumber *)node resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker:(nonnull NSNumber *)node)
|
||||
|
||||
@end
|
||||
|
||||
@interface RCT_EXTERN_MODULE(KSPlayerModule, RCTEventEmitter)
|
||||
|
||||
RCT_EXTERN_METHOD(getTracks:(NSNumber *)nodeTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(getAirPlayState:(NSNumber *)nodeTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker:(NSNumber *)nodeTag)
|
||||
|
||||
@end
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
//
|
||||
// KSPlayerModule.swift
|
||||
// Nuvio
|
||||
//
|
||||
// Created by KSPlayer integration
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KSPlayer
|
||||
import React
|
||||
|
||||
@objc(KSPlayerModule)
|
||||
class KSPlayerModule: RCTEventEmitter {
|
||||
override static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func supportedEvents() -> [String]! {
|
||||
return [
|
||||
"KSPlayer-onLoad",
|
||||
"KSPlayer-onProgress",
|
||||
"KSPlayer-onBuffering",
|
||||
"KSPlayer-onEnd",
|
||||
"KSPlayer-onError"
|
||||
]
|
||||
}
|
||||
|
||||
@objc func getTracks(_ nodeTag: NSNumber?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
guard let nodeTag = nodeTag else {
|
||||
reject("INVALID_ARGUMENT", "nodeTag must not be nil", nil)
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if let viewManager = self.bridge.module(for: KSPlayerViewManager.self) as? KSPlayerViewManager {
|
||||
viewManager.getTracks(nodeTag, resolve: resolve, reject: reject)
|
||||
} else {
|
||||
reject("NO_VIEW_MANAGER", "KSPlayerViewManager not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getAirPlayState(_ nodeTag: NSNumber?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
guard let nodeTag = nodeTag else {
|
||||
reject("INVALID_ARGUMENT", "nodeTag must not be nil", nil)
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if let viewManager = self.bridge.module(for: KSPlayerViewManager.self) as? KSPlayerViewManager {
|
||||
viewManager.getAirPlayState(nodeTag, resolve: resolve, reject: reject)
|
||||
} else {
|
||||
reject("NO_VIEW_MANAGER", "KSPlayerViewManager not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showAirPlayPicker(_ nodeTag: NSNumber?) {
|
||||
guard let nodeTag = nodeTag else {
|
||||
print("[KSPlayerModule] showAirPlayPicker called with nil nodeTag")
|
||||
return
|
||||
}
|
||||
print("[KSPlayerModule] showAirPlayPicker called for nodeTag: \(nodeTag)")
|
||||
DispatchQueue.main.async {
|
||||
if let viewManager = self.bridge.module(for: KSPlayerViewManager.self) as? KSPlayerViewManager {
|
||||
print("[KSPlayerModule] Found KSPlayerViewManager, calling showAirPlayPicker")
|
||||
viewManager.showAirPlayPicker(nodeTag)
|
||||
} else {
|
||||
print("[KSPlayerModule] Could not find KSPlayerViewManager")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,152 +0,0 @@
|
|||
//
|
||||
// KSPlayerViewManager.swift
|
||||
// Nuvio
|
||||
//
|
||||
// Created by KSPlayer integration
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KSPlayer
|
||||
import React
|
||||
|
||||
@objc(KSPlayerViewManager)
|
||||
class KSPlayerViewManager: RCTViewManager {
|
||||
|
||||
// Not needed for RCTViewManager-based views; events are exported via Objective-C externs in KSPlayerManager.m
|
||||
override func view() -> UIView! {
|
||||
let view = KSPlayerView()
|
||||
view.viewManager = self
|
||||
return view
|
||||
}
|
||||
|
||||
override static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func constantsToExport() -> [AnyHashable : Any]! {
|
||||
return [
|
||||
"EventTypes": [
|
||||
"onLoad": "onLoad",
|
||||
"onProgress": "onProgress",
|
||||
"onBuffering": "onBuffering",
|
||||
"onEnd": "onEnd",
|
||||
"onError": "onError",
|
||||
"onBufferingProgress": "onBufferingProgress"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
// No-op: events are sent via direct event blocks on the view
|
||||
|
||||
@objc func seek(_ node: NSNumber, toTime time: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.seek(to: TimeInterval(truncating: time))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setSource(_ node: NSNumber, source: NSDictionary) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setSource(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setPaused(_ node: NSNumber, paused: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setPaused(paused)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setVolume(_ node: NSNumber, volume: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setVolume(Float(truncating: volume))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setPlaybackRate(_ node: NSNumber, rate: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setPlaybackRate(Float(truncating: rate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setAudioTrack(_ node: NSNumber, trackId: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setAudioTrack(Int(truncating: trackId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setTextTrack(_ node: NSNumber, trackId: NSNumber) {
|
||||
NSLog("[KSPlayerViewManager] setTextTrack called - node: %@, trackId: %@", node, trackId)
|
||||
DispatchQueue.main.async {
|
||||
NSLog("[KSPlayerViewManager] setTextTrack on main queue - looking for view with tag: %@", node)
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
NSLog("[KSPlayerViewManager] Found view, calling setTextTrack(%d)", Int(truncating: trackId))
|
||||
view.setTextTrack(Int(truncating: trackId))
|
||||
} else {
|
||||
NSLog("[KSPlayerViewManager] ERROR - Could not find KSPlayerView for tag: %@", node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getTracks(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
let tracks = view.getAvailableTracks()
|
||||
resolve(tracks)
|
||||
} else {
|
||||
reject("NO_VIEW", "KSPlayerView not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AirPlay methods
|
||||
@objc func setAllowsExternalPlayback(_ node: NSNumber, allows: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setAllowsExternalPlayback(allows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setUsesExternalPlaybackWhileExternalScreenIsActive(_ node: NSNumber, uses: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setUsesExternalPlaybackWhileExternalScreenIsActive(uses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getAirPlayState(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
let airPlayState = view.getAirPlayState()
|
||||
resolve(airPlayState)
|
||||
} else {
|
||||
reject("NO_VIEW", "KSPlayerView not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showAirPlayPicker(_ node: NSNumber) {
|
||||
print("[KSPlayerViewManager] showAirPlayPicker called for node: \(node)")
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
print("[KSPlayerViewManager] Found KSPlayerView, calling showAirPlayPicker")
|
||||
view.showAirPlayPicker()
|
||||
} else {
|
||||
print("[KSPlayerViewManager] Could not find KSPlayerView for node: \(node)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,15 +7,11 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0FFC28FB1FEA74CCFA112268 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 49055D6E250FAFA21141FE49 /* PrivacyInfo.xcprivacy */; };
|
||||
10494EC15A10E131C2A5B84D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66BF1B6C10F73045E83634B3 /* ExpoModulesProvider.swift */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
2AA769395C1242F225F875AF /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */; };
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
|
||||
564F8559E25775FFA08707DA /* libPods-Nuvio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18260C6F06D8D6DAF4C1D17E /* libPods-Nuvio.a */; };
|
||||
9FBA88F42E86ECD700892850 /* KSPlayerViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */; };
|
||||
9FBA88F52E86ECD700892850 /* KSPlayerModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */; };
|
||||
9FBA88F62E86ECD700892850 /* KSPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F02E86ECD700892850 /* KSPlayerManager.m */; };
|
||||
9FBA88F72E86ECD700892850 /* KSPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F22E86ECD700892850 /* KSPlayerView.swift */; };
|
||||
5CF17A388C0A9A5F3C3CC900 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1B7D0CBC765F9DB40F1542C5 /* PrivacyInfo.xcprivacy */; };
|
||||
90AAC6A10D59CBB4DE99A185 /* libPods-Nuvio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A4DAA93D0222D02AE23FE3C /* libPods-Nuvio.a */; };
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
||||
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
|
@ -24,16 +20,11 @@
|
|||
13B07F961A680F5B00A75B9A /* Nuvio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Nuvio.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Nuvio/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Nuvio/Info.plist; sourceTree = "<group>"; };
|
||||
18260C6F06D8D6DAF4C1D17E /* libPods-Nuvio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Nuvio.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2118C3C63E4B7D66EAC534DE /* Pods-Nuvio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.debug.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
49055D6E250FAFA21141FE49 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Nuvio/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Nuvio/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
73BB213C2E9EEAC700EC03F8 /* NuvioRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NuvioRelease.entitlements; path = Nuvio/NuvioRelease.entitlements; sourceTree = "<group>"; };
|
||||
7F2FA62198C389C99926AA47 /* Pods-Nuvio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.release.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.release.xcconfig"; sourceTree = "<group>"; };
|
||||
9FBA88F02E86ECD700892850 /* KSPlayerManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSPlayerManager.m; sourceTree = "<group>"; };
|
||||
9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSPlayerModule.swift; sourceTree = "<group>"; };
|
||||
9FBA88F22E86ECD700892850 /* KSPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSPlayerView.swift; sourceTree = "<group>"; };
|
||||
9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSPlayerViewManager.swift; sourceTree = "<group>"; };
|
||||
13B340DA5D39E8F6194D4A29 /* Pods-Nuvio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.release.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.release.xcconfig"; sourceTree = "<group>"; };
|
||||
1B7D0CBC765F9DB40F1542C5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Nuvio/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
59D018C54181F6CC8CB0057C /* Pods-Nuvio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.debug.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
66BF1B6C10F73045E83634B3 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Nuvio/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
9A4DAA93D0222D02AE23FE3C /* libPods-Nuvio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Nuvio.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Nuvio/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
|
|
@ -46,7 +37,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
564F8559E25775FFA08707DA /* libPods-Nuvio.a in Frameworks */,
|
||||
90AAC6A10D59CBB4DE99A185 /* libPods-Nuvio.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -56,18 +47,13 @@
|
|||
13B07FAE1A68108700A75B9A /* Nuvio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
73BB213C2E9EEAC700EC03F8 /* NuvioRelease.entitlements */,
|
||||
F11748412D0307B40044C1D9 /* AppDelegate.swift */,
|
||||
F11748442D0722820044C1D9 /* Nuvio-Bridging-Header.h */,
|
||||
9FBA88F02E86ECD700892850 /* KSPlayerManager.m */,
|
||||
9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */,
|
||||
9FBA88F22E86ECD700892850 /* KSPlayerView.swift */,
|
||||
9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */,
|
||||
BB2F792B24A3F905000567C9 /* Supporting */,
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
||||
49055D6E250FAFA21141FE49 /* PrivacyInfo.xcprivacy */,
|
||||
1B7D0CBC765F9DB40F1542C5 /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
name = Nuvio;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -76,15 +62,25 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
18260C6F06D8D6DAF4C1D17E /* libPods-Nuvio.a */,
|
||||
9A4DAA93D0222D02AE23FE3C /* libPods-Nuvio.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
358C5C99C443A921C8EEDDC8 /* ExpoModulesProviders */ = {
|
||||
7DE8F3BB285224D25F88E2AF /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ECB31D9B6FF08C7E8E875650 /* Nuvio */,
|
||||
59D018C54181F6CC8CB0057C /* Pods-Nuvio.debug.xcconfig */,
|
||||
13B340DA5D39E8F6194D4A29 /* Pods-Nuvio.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F73664F3B4B3A59A03D5045 /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97D6A03D95036375A7C4B3D0 /* Nuvio */,
|
||||
);
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -103,8 +99,8 @@
|
|||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
D90A3959C97EE9926C513293 /* Pods */,
|
||||
358C5C99C443A921C8EEDDC8 /* ExpoModulesProviders */,
|
||||
7DE8F3BB285224D25F88E2AF /* Pods */,
|
||||
7F73664F3B4B3A59A03D5045 /* ExpoModulesProviders */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -119,6 +115,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97D6A03D95036375A7C4B3D0 /* Nuvio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
66BF1B6C10F73045E83634B3 /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = Nuvio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BB2F792B24A3F905000567C9 /* Supporting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -128,23 +132,6 @@
|
|||
path = Nuvio/Supporting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D90A3959C97EE9926C513293 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2118C3C63E4B7D66EAC534DE /* Pods-Nuvio.debug.xcconfig */,
|
||||
7F2FA62198C389C99926AA47 /* Pods-Nuvio.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
ECB31D9B6FF08C7E8E875650 /* Nuvio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = Nuvio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -152,15 +139,15 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Nuvio" */;
|
||||
buildPhases = (
|
||||
4A10611824FCBAA4C1793637 /* [CP] Check Pods Manifest.lock */,
|
||||
99A79B70155E84EE1FB7F466 /* [Expo] Configure project */,
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
||||
C1F70A9254487D3D09AEC536 /* [Expo] Configure project */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
9B977D89FE30470F8C59964C /* Upload Debug Symbols to Sentry */,
|
||||
EE80421364369BBCA82253B9 /* [CP] Embed Pods Frameworks */,
|
||||
778B42B39FEE5454E4D24252 /* [CP] Copy Pods Resources */,
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||
BC6182A9E2AB4627A4DEC380 /* Upload Debug Symbols to Sentry */,
|
||||
21592BEE6E404328F78AD757 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
|
@ -181,6 +168,8 @@
|
|||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1250;
|
||||
DevelopmentTeam = "8QBDZ766S3";
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -210,7 +199,7 @@
|
|||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
||||
0FFC28FB1FEA74CCFA112268 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
5CF17A388C0A9A5F3C3CC900 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -234,7 +223,7 @@
|
|||
shellPath = /bin/sh;
|
||||
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n/bin/sh `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode.sh'\"` `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
|
||||
};
|
||||
4A10611824FCBAA4C1793637 /* [CP] Check Pods Manifest.lock */ = {
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
|
|
@ -256,7 +245,29 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
778B42B39FEE5454E4D24252 /* [CP] Copy Pods Resources */ = {
|
||||
21592BEE6E404328F78AD757 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh",
|
||||
"${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}/React.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
|
|
@ -266,14 +277,11 @@
|
|||
"${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoLocalization/ExpoLocalization_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/KSPlayer/KSPlayer_KSPlayer.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
|
||||
|
|
@ -301,14 +309,6 @@
|
|||
"${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-menu/EXDevMenu.bundle",
|
||||
"${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleCastCoreResources.bundle",
|
||||
"${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleCastUIResources.bundle",
|
||||
"${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleCastOptionalUIResources.bundle",
|
||||
"${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/MaterialDialogs.bundle",
|
||||
"${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleSansBold.bundle",
|
||||
"${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleSansMedium.bundle",
|
||||
"${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleSansRegular.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/google-cast-sdk/GoogleCast.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/LottiePrivacyInfo.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/lottie-react-native/Lottie_React_Native_Privacy.bundle",
|
||||
);
|
||||
|
|
@ -317,14 +317,11 @@
|
|||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoLocalization_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/KSPlayer_KSPlayer.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
|
||||
|
|
@ -352,14 +349,6 @@
|
|||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastCoreResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastUIResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastOptionalUIResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialDialogs.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSansBold.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSansMedium.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSansRegular.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCast.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/LottiePrivacyInfo.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Lottie_React_Native_Privacy.bundle",
|
||||
);
|
||||
|
|
@ -368,7 +357,21 @@
|
|||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
99A79B70155E84EE1FB7F466 /* [Expo] Configure project */ = {
|
||||
BC6182A9E2AB4627A4DEC380 /* Upload Debug Symbols to Sentry */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Upload Debug Symbols to Sentry";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
|
||||
};
|
||||
C1F70A9254487D3D09AEC536 /* [Expo] Configure project */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -392,42 +395,6 @@
|
|||
shellPath = /bin/sh;
|
||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Nuvio/expo-configure-project.sh\"\n";
|
||||
};
|
||||
9B977D89FE30470F8C59964C /* Upload Debug Symbols to Sentry */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Upload Debug Symbols to Sentry";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
|
||||
};
|
||||
EE80421364369BBCA82253B9 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh",
|
||||
"${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}/React.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
|
@ -436,11 +403,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
|
||||
9FBA88F42E86ECD700892850 /* KSPlayerViewManager.swift in Sources */,
|
||||
9FBA88F52E86ECD700892850 /* KSPlayerModule.swift in Sources */,
|
||||
9FBA88F62E86ECD700892850 /* KSPlayerManager.m in Sources */,
|
||||
9FBA88F72E86ECD700892850 /* KSPlayerView.swift in Sources */,
|
||||
2AA769395C1242F225F875AF /* ExpoModulesProvider.swift in Sources */,
|
||||
10494EC15A10E131C2A5B84D /* ExpoModulesProvider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -449,13 +412,12 @@
|
|||
/* Begin XCBuildConfiguration section */
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 2118C3C63E4B7D66EAC534DE /* Pods-Nuvio.debug.xcconfig */;
|
||||
baseConfigurationReference = 59D018C54181F6CC8CB0057C /* Pods-Nuvio.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Nuvio/Nuvio.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 8QBDZ766S3;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
|
|
@ -476,24 +438,27 @@
|
|||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 15.1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
DEVELOPMENT_TEAM = "8QBDZ766S3";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7F2FA62198C389C99926AA47 /* Pods-Nuvio.release.xcconfig */;
|
||||
baseConfigurationReference = 13B340DA5D39E8F6194D4A29 /* Pods-Nuvio.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Nuvio/NuvioRelease.entitlements;
|
||||
CODE_SIGN_ENTITLEMENTS = Nuvio/Nuvio.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 8QBDZ766S3;
|
||||
INFOPLIST_FILE = Nuvio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
|
@ -507,13 +472,17 @@
|
|||
"-lc++",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 15.1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
DEVELOPMENT_TEAM = "8QBDZ766S3";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -573,7 +542,7 @@
|
|||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
USE_HERMES = true;
|
||||
|
|
@ -628,7 +597,7 @@
|
|||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
USE_HERMES = true;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
|
|
|||
|
|
@ -1,110 +1,103 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nuvio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.10</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>nuvio</string>
|
||||
<string>com.nuvio.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>exp+nuvio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>29</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_http._tcp</string>
|
||||
<string>_googlecast._tcp</string>
|
||||
<string>_CC1AD845._googlecast._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app does not require microphone access.</string>
|
||||
<key>RCTNewArchEnabled</key>
|
||||
<true/>
|
||||
<key>RCTRootViewBackgroundColor</key>
|
||||
<integer>4278322180</integer>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict>
|
||||
<key>UIColorName</key>
|
||||
<string>SplashScreenBackground</string>
|
||||
<key>UIImageName</key>
|
||||
<string>SplashScreenLegacy</string>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nuvio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>nuvio</string>
|
||||
<string>com.nuvio.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>exp+nuvio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>29</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_http._tcp</string>
|
||||
<string>_googlecast._tcp</string>
|
||||
<string>_CC1AD845._googlecast._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Nuvio uses the local network to discover Cast-enabled devices on your WiFi network and to connect to local media servers.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app does not require microphone access.</string>
|
||||
<key>RCTNewArchEnabled</key>
|
||||
<true/>
|
||||
<key>RCTRootViewBackgroundColor</key>
|
||||
<integer>4278322180</integer>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -20,7 +20,6 @@
|
|||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
<string>C56D.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
|
||||
<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="24093.7" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<deployment identifier="tvOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24053.1"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
|
||||
<view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView id="EXPO-SplashScreen" userLabel="SplashScreenLegacy" image="SplashScreenLegacy" contentMode="scaleAspectFit" clipsSubviews="true" userInteractionEnabled="false" translatesAutoresizingMaskIntoConstraints="false">
|
||||
<rect key="frame" x="0" y="0" width="414" height="736"/>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<key>EXUpdatesLaunchWaitMs</key>
|
||||
<integer>30000</integer>
|
||||
<key>EXUpdatesRuntimeVersion</key>
|
||||
<string>1.2.11</string>
|
||||
<string>1.3.1</string>
|
||||
<key>EXUpdatesURL</key>
|
||||
<string>https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest</string>
|
||||
</dict>
|
||||
|
|
|
|||
111
ios/Podfile
111
ios/Podfile
|
|
@ -16,12 +16,12 @@ ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == '
|
|||
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
|
||||
ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
|
||||
ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
|
||||
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
|
||||
platform :tvos, podfile_properties['ios.deploymentTarget'] || '15.1'
|
||||
|
||||
prepare_react_native_project!
|
||||
|
||||
target 'Nuvio' do
|
||||
use_expo_modules!(exclude: ['expo-libvlc-player'])
|
||||
use_expo_modules!
|
||||
|
||||
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
|
||||
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
|
||||
|
|
@ -49,12 +49,6 @@ target 'Nuvio' do
|
|||
:privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
|
||||
)
|
||||
|
||||
# KSPlayer dependencies
|
||||
pod 'KSPlayer', :git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main'
|
||||
pod 'DisplayCriteria', :git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main', :modular_headers => true
|
||||
pod 'FFmpegKit', :git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main', :modular_headers => true
|
||||
pod 'Libass', :git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main'
|
||||
|
||||
post_install do |installer|
|
||||
react_native_post_install(
|
||||
installer,
|
||||
|
|
@ -62,5 +56,106 @@ target 'Nuvio' do
|
|||
:mac_catalyst_enabled => false,
|
||||
:ccache_enabled => ccache_enabled?(podfile_properties),
|
||||
)
|
||||
|
||||
# Patch RCTThirdPartyComponentsProvider.mm to filter out nil component classes (tvOS compatibility)
|
||||
# Some third-party libraries don't have Fabric components on tvOS, causing crashes
|
||||
provider_file = "#{Pod::Config.instance.installation_root}/build/generated/ios/RCTThirdPartyComponentsProvider.mm"
|
||||
if File.exist?(provider_file)
|
||||
puts "Patching RCTThirdPartyComponentsProvider.mm for tvOS compatibility..."
|
||||
content = File.read(provider_file)
|
||||
|
||||
# Replace the dictionary literal with a mutable dictionary approach that filters nil values
|
||||
patched_content = content.gsub(
|
||||
/\+ \(NSDictionary<NSString \*, Class<RCTComponentViewProtocol>>\s*\*\)thirdPartyFabricComponents\s*\{.*?return thirdPartyComponents;\s*\}/m,
|
||||
<<~OBJC
|
||||
+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
|
||||
{
|
||||
static NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *thirdPartyComponents = nil;
|
||||
static dispatch_once_t nativeComponentsToken;
|
||||
|
||||
dispatch_once(&nativeComponentsToken, ^{
|
||||
NSMutableDictionary<NSString *, Class<RCTComponentViewProtocol>> *components = [NSMutableDictionary new];
|
||||
|
||||
// Helper macro to safely add components (skips nil classes for tvOS compatibility)
|
||||
#define ADD_COMPONENT(name, className) \\
|
||||
do { \\
|
||||
Class cls = NSClassFromString(className); \\
|
||||
if (cls != nil) { \\
|
||||
components[name] = cls; \\
|
||||
} \\
|
||||
} while(0)
|
||||
|
||||
ADD_COMPONENT(@"FastImageView", @"FFFastImageViewComponentView");
|
||||
ADD_COMPONENT(@"RNCSlider", @"RNCSliderComponentView");
|
||||
ADD_COMPONENT(@"RNCPicker", @"RNCPickerComponentView");
|
||||
ADD_COMPONENT(@"LottieAnimationView", @"LottieAnimationViewComponentView");
|
||||
ADD_COMPONENT(@"RNCTabView", @"RCTTabViewComponentView");
|
||||
ADD_COMPONENT(@"BottomAccessoryView", @"RCTBottomAccessoryComponentView");
|
||||
ADD_COMPONENT(@"RNGestureHandlerButton", @"RNGestureHandlerButtonComponentView");
|
||||
ADD_COMPONENT(@"RNCSafeAreaProvider", @"RNCSafeAreaProviderComponentView");
|
||||
ADD_COMPONENT(@"RNCSafeAreaView", @"RNCSafeAreaViewComponentView");
|
||||
ADD_COMPONENT(@"RNSVGCircle", @"RNSVGCircle");
|
||||
ADD_COMPONENT(@"RNSVGClipPath", @"RNSVGClipPath");
|
||||
ADD_COMPONENT(@"RNSVGDefs", @"RNSVGDefs");
|
||||
ADD_COMPONENT(@"RNSVGEllipse", @"RNSVGEllipse");
|
||||
ADD_COMPONENT(@"RNSVGFeBlend", @"RNSVGFeBlend");
|
||||
ADD_COMPONENT(@"RNSVGFeColorMatrix", @"RNSVGFeColorMatrix");
|
||||
ADD_COMPONENT(@"RNSVGFeComposite", @"RNSVGFeComposite");
|
||||
ADD_COMPONENT(@"RNSVGFeFlood", @"RNSVGFeFlood");
|
||||
ADD_COMPONENT(@"RNSVGFeGaussianBlur", @"RNSVGFeGaussianBlur");
|
||||
ADD_COMPONENT(@"RNSVGFeMerge", @"RNSVGFeMerge");
|
||||
ADD_COMPONENT(@"RNSVGFeOffset", @"RNSVGFeOffset");
|
||||
ADD_COMPONENT(@"RNSVGFilter", @"RNSVGFilter");
|
||||
ADD_COMPONENT(@"RNSVGForeignObject", @"RNSVGForeignObject");
|
||||
ADD_COMPONENT(@"RNSVGGroup", @"RNSVGGroup");
|
||||
ADD_COMPONENT(@"RNSVGImage", @"RNSVGImage");
|
||||
ADD_COMPONENT(@"RNSVGLine", @"RNSVGLine");
|
||||
ADD_COMPONENT(@"RNSVGLinearGradient", @"RNSVGLinearGradient");
|
||||
ADD_COMPONENT(@"RNSVGMarker", @"RNSVGMarker");
|
||||
ADD_COMPONENT(@"RNSVGMask", @"RNSVGMask");
|
||||
ADD_COMPONENT(@"RNSVGPath", @"RNSVGPath");
|
||||
ADD_COMPONENT(@"RNSVGPattern", @"RNSVGPattern");
|
||||
ADD_COMPONENT(@"RNSVGRadialGradient", @"RNSVGRadialGradient");
|
||||
ADD_COMPONENT(@"RNSVGRect", @"RNSVGRect");
|
||||
ADD_COMPONENT(@"RNSVGSvgView", @"RNSVGSvgView");
|
||||
ADD_COMPONENT(@"RNSVGSymbol", @"RNSVGSymbol");
|
||||
ADD_COMPONENT(@"RNSVGTSpan", @"RNSVGTSpan");
|
||||
ADD_COMPONENT(@"RNSVGText", @"RNSVGText");
|
||||
ADD_COMPONENT(@"RNSVGTextPath", @"RNSVGTextPath");
|
||||
ADD_COMPONENT(@"RNSVGUse", @"RNSVGUse");
|
||||
ADD_COMPONENT(@"RNSFullWindowOverlay", @"RNSFullWindowOverlay");
|
||||
ADD_COMPONENT(@"RNSModalScreen", @"RNSModalScreen");
|
||||
ADD_COMPONENT(@"RNSScreenContainer", @"RNSScreenContainerView");
|
||||
ADD_COMPONENT(@"RNSScreenContentWrapper", @"RNSScreenContentWrapper");
|
||||
ADD_COMPONENT(@"RNSScreenFooter", @"RNSScreenFooter");
|
||||
ADD_COMPONENT(@"RNSScreen", @"RNSScreenView");
|
||||
ADD_COMPONENT(@"RNSScreenNavigationContainer", @"RNSScreenNavigationContainerView");
|
||||
ADD_COMPONENT(@"RNSScreenStackHeaderConfig", @"RNSScreenStackHeaderConfig");
|
||||
ADD_COMPONENT(@"RNSScreenStackHeaderSubview", @"RNSScreenStackHeaderSubview");
|
||||
ADD_COMPONENT(@"RNSScreenStack", @"RNSScreenStackView");
|
||||
ADD_COMPONENT(@"RNSSearchBar", @"RNSSearchBar");
|
||||
ADD_COMPONENT(@"RNSStackScreen", @"RNSStackScreenComponentView");
|
||||
ADD_COMPONENT(@"RNSScreenStackHost", @"RNSScreenStackHostComponentView");
|
||||
ADD_COMPONENT(@"RNSBottomTabsScreen", @"RNSBottomTabsScreenComponentView");
|
||||
ADD_COMPONENT(@"RNSBottomTabs", @"RNSBottomTabsHostComponentView");
|
||||
ADD_COMPONENT(@"RNSBottomTabsAccessory", @"RNSBottomTabsAccessoryComponentView");
|
||||
ADD_COMPONENT(@"RNSBottomTabsAccessoryContent", @"RNSBottomTabsAccessoryContentComponentView");
|
||||
ADD_COMPONENT(@"RNSSplitViewHost", @"RNSSplitViewHostComponentView");
|
||||
ADD_COMPONENT(@"RNSSplitViewScreen", @"RNSSplitViewScreenComponentView");
|
||||
ADD_COMPONENT(@"RNSSafeAreaView", @"RNSSafeAreaViewComponentView");
|
||||
|
||||
#undef ADD_COMPONENT
|
||||
|
||||
thirdPartyComponents = [components copy];
|
||||
});
|
||||
|
||||
return thirdPartyComponents;
|
||||
}
|
||||
OBJC
|
||||
)
|
||||
|
||||
File.write(provider_file, patched_content)
|
||||
puts "Patched RCTThirdPartyComponentsProvider.mm successfully!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
888
ios/Podfile.lock
888
ios/Podfile.lock
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
889
package-lock.json
generated
889
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
|
@ -67,7 +67,7 @@
|
|||
"lottie-react-native": "~7.3.1",
|
||||
"posthog-react-native": "^4.4.0",
|
||||
"react": "19.1.0",
|
||||
"react-native": "0.81.4",
|
||||
"react-native": "npm:react-native-tvos@0.81-stable",
|
||||
"react-native-boost": "^0.6.2",
|
||||
"react-native-bottom-tabs": "^1.0.2",
|
||||
"react-native-gesture-handler": "^2.29.1",
|
||||
|
|
@ -93,6 +93,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@react-native-tvos/config-tv": "^0.1.4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/react": "~18.3.12",
|
||||
"@types/react-native": "^0.72.8",
|
||||
|
|
@ -103,5 +104,12 @@
|
|||
"typescript": "^5.3.3",
|
||||
"xcode": "^3.0.1"
|
||||
},
|
||||
"expo": {
|
||||
"install": {
|
||||
"exclude": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
|
|
|||
87
patches/react-native-bottom-tabs+1.1.0.patch
Normal file
87
patches/react-native-bottom-tabs+1.1.0.patch
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
diff --git a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||
index 539efee..dc3f2fd 100644
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||
@@ -8,8 +8,8 @@ import SwiftUI
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
- #if !os(macOS)
|
||||
- @available(iOS 26.0, *)
|
||||
+ #if !os(macOS) && !os(tvOS)
|
||||
+ @available(iOS 26.0, visionOS 3.0, *)
|
||||
public func emitPlacementChanged(_ placement: TabViewBottomAccessoryPlacement?) {
|
||||
var placementValue = "none"
|
||||
if placement == .inline {
|
||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||
index d699315..a78689a 100644
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||
@@ -67,11 +67,11 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
- #if os(macOS)
|
||||
- // tabViewBottomAccessory is not available on macOS
|
||||
+ #if os(macOS) || os(tvOS)
|
||||
+ // tabViewBottomAccessory is not available on macOS or tvOS
|
||||
content
|
||||
#else
|
||||
- if #available(iOS 26.0, tvOS 26.0, visionOS 3.0, *), bottomAccessoryView != nil {
|
||||
+ if #available(iOS 26.0, visionOS 3.0, *), bottomAccessoryView != nil {
|
||||
content
|
||||
.tabViewBottomAccessory {
|
||||
renderBottomAccessoryView()
|
||||
@@ -84,9 +84,9 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
||||
|
||||
@ViewBuilder
|
||||
private func renderBottomAccessoryView() -> some View {
|
||||
- #if !os(macOS)
|
||||
+ #if !os(macOS) && !os(tvOS)
|
||||
if let bottomAccessoryView {
|
||||
- if #available(iOS 26.0, *) {
|
||||
+ if #available(iOS 26.0, visionOS 3.0, *) {
|
||||
BottomAccessoryRepresentableView(view: bottomAccessoryView)
|
||||
}
|
||||
}
|
||||
@@ -94,8 +94,8 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
||||
}
|
||||
}
|
||||
|
||||
-#if !os(macOS)
|
||||
-@available(iOS 26.0, *)
|
||||
+#if !os(macOS) && !os(tvOS)
|
||||
+@available(iOS 26.0, visionOS 3.0, *)
|
||||
struct BottomAccessoryRepresentableView: PlatformViewRepresentable {
|
||||
@Environment(\.tabViewBottomAccessoryPlacement) var tabViewBottomAccessoryPlacement
|
||||
var view: PlatformView
|
||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||
index 72938be..f8325bb 100644
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||
@@ -281,8 +281,8 @@ extension View {
|
||||
|
||||
@ViewBuilder
|
||||
func tabBarMinimizeBehavior(_ behavior: MinimizeBehavior?) -> some View {
|
||||
- #if compiler(>=6.2)
|
||||
- if #available(iOS 26.0, macOS 26.0, *) {
|
||||
+ #if compiler(>=6.2) && !os(tvOS)
|
||||
+ if #available(iOS 26.0, macOS 26.0, visionOS 3.0, *) {
|
||||
if let behavior {
|
||||
self.tabBarMinimizeBehavior(behavior.convert())
|
||||
} else {
|
||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||
index cd098c0..4f90598 100644
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||
@@ -6,8 +6,8 @@ internal enum MinimizeBehavior: String {
|
||||
case onScrollUp
|
||||
case onScrollDown
|
||||
|
||||
-#if compiler(>=6.2)
|
||||
- @available(iOS 26.0, macOS 26.0, *)
|
||||
+#if compiler(>=6.2) && !os(tvOS)
|
||||
+ @available(iOS 26.0, macOS 26.0, visionOS 3.0, *)
|
||||
func convert() -> TabBarMinimizeBehavior {
|
||||
#if os(macOS)
|
||||
return .automatic
|
||||
|
|
@ -1,13 +1,28 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
import { StatusBar, Platform, Dimensions, AppState } from 'react-native';
|
||||
import RNImmersiveMode from 'react-native-immersive-mode';
|
||||
import * as NavigationBar from 'expo-navigation-bar';
|
||||
import * as Brightness from 'expo-brightness';
|
||||
import { activateKeepAwakeAsync, deactivateKeepAwake } from 'expo-keep-awake';
|
||||
import { logger } from '../../../../utils/logger';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
// Check if running on TV
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import modules not available on Android TV
|
||||
let RNImmersiveMode: any = null;
|
||||
let NavigationBar: typeof import('expo-navigation-bar') | null = null;
|
||||
let Brightness: typeof import('expo-brightness') | null = null;
|
||||
|
||||
if (!isTV) {
|
||||
try {
|
||||
RNImmersiveMode = require('react-native-immersive-mode').default;
|
||||
NavigationBar = require('expo-navigation-bar');
|
||||
Brightness = require('expo-brightness');
|
||||
} catch (e) {
|
||||
logger.warn('[usePlayerSetup] Some player modules not available:', e);
|
||||
}
|
||||
}
|
||||
|
||||
const DEBUG_MODE = false;
|
||||
|
||||
export const usePlayerSetup = (
|
||||
|
|
@ -34,32 +49,40 @@ export const usePlayerSetup = (
|
|||
}, [paused]);
|
||||
|
||||
const enableImmersiveMode = async () => {
|
||||
if (Platform.OS === 'android') {
|
||||
// Standard immersive mode
|
||||
RNImmersiveMode.setBarTranslucent(true);
|
||||
RNImmersiveMode.fullLayout(true);
|
||||
if (Platform.OS === 'android' && !isTV) {
|
||||
// Standard immersive mode (not available on TV)
|
||||
if (RNImmersiveMode) {
|
||||
RNImmersiveMode.setBarTranslucent(true);
|
||||
RNImmersiveMode.fullLayout(true);
|
||||
}
|
||||
StatusBar.setHidden(true, 'none');
|
||||
|
||||
// Explicitly hide bottom navigation bar using Expo
|
||||
try {
|
||||
await NavigationBar.setVisibilityAsync("hidden");
|
||||
await NavigationBar.setBehaviorAsync("overlay-swipe");
|
||||
} catch (e) {
|
||||
// Ignore errors on non-supported devices
|
||||
if (NavigationBar) {
|
||||
try {
|
||||
await NavigationBar.setVisibilityAsync("hidden");
|
||||
await NavigationBar.setBehaviorAsync("overlay-swipe");
|
||||
} catch (e) {
|
||||
// Ignore errors on non-supported devices
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const disableImmersiveMode = async () => {
|
||||
if (Platform.OS === 'android') {
|
||||
RNImmersiveMode.setBarTranslucent(false);
|
||||
RNImmersiveMode.fullLayout(false);
|
||||
if (Platform.OS === 'android' && !isTV) {
|
||||
if (RNImmersiveMode) {
|
||||
RNImmersiveMode.setBarTranslucent(false);
|
||||
RNImmersiveMode.fullLayout(false);
|
||||
}
|
||||
StatusBar.setHidden(false, 'fade');
|
||||
|
||||
try {
|
||||
await NavigationBar.setVisibilityAsync("visible");
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
if (NavigationBar) {
|
||||
try {
|
||||
await NavigationBar.setVisibilityAsync("visible");
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -84,10 +107,14 @@ export const usePlayerSetup = (
|
|||
// Initialize volume (default to 1.0)
|
||||
setVolume(1.0);
|
||||
|
||||
// Initialize Brightness
|
||||
// Initialize Brightness (skip on TV)
|
||||
const initBrightness = async () => {
|
||||
if (!Brightness) {
|
||||
setBrightness(1.0);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (Platform.OS === 'android') {
|
||||
if (Platform.OS === 'android' && !isTV) {
|
||||
try {
|
||||
const [sysBright, sysMode] = await Promise.all([
|
||||
(Brightness as any).getSystemBrightnessAsync?.(),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,25 @@
|
|||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { StatusBar, Dimensions, AppState, InteractionManager, Platform } from 'react-native';
|
||||
import * as Brightness from 'expo-brightness';
|
||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { activateKeepAwakeAsync, deactivateKeepAwake } from 'expo-keep-awake';
|
||||
import { logger } from '../../../utils/logger';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
|
||||
// Check if running on TV
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import Brightness and ScreenOrientation (not available on TV)
|
||||
let Brightness: typeof import('expo-brightness') | null = null;
|
||||
let ScreenOrientation: typeof import('expo-screen-orientation') | null = null;
|
||||
|
||||
if (!isTV) {
|
||||
try {
|
||||
Brightness = require('expo-brightness');
|
||||
ScreenOrientation = require('expo-screen-orientation');
|
||||
} catch (e) {
|
||||
logger.warn('[usePlayerSetup] Brightness/ScreenOrientation not available:', e);
|
||||
}
|
||||
}
|
||||
|
||||
interface PlayerSetupConfig {
|
||||
setScreenDimensions: (dim: any) => void;
|
||||
setVolume: (vol: number) => void;
|
||||
|
|
@ -72,11 +86,15 @@ export const usePlayerSetup = (config: PlayerSetupConfig) => {
|
|||
// Initialize volume (normalized 0-1 for cross-platform)
|
||||
setVolume(1.0);
|
||||
|
||||
// Initialize Brightness
|
||||
// Initialize Brightness (skip on TV)
|
||||
const initBrightness = () => {
|
||||
if (!Brightness) {
|
||||
setBrightness(1.0);
|
||||
return;
|
||||
}
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
try {
|
||||
const currentBrightness = await Brightness.getBrightnessAsync();
|
||||
const currentBrightness = await Brightness!.getBrightnessAsync();
|
||||
setBrightness(currentBrightness);
|
||||
} catch (error) {
|
||||
logger.warn('[usePlayerSetup] Error getting initial brightness:', error);
|
||||
|
|
@ -95,9 +113,12 @@ export const usePlayerSetup = (config: PlayerSetupConfig) => {
|
|||
const orientationLocked = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Skip orientation lock on TV (not needed)
|
||||
if (!ScreenOrientation) return;
|
||||
|
||||
if (isOpeningAnimationComplete && !orientationLocked.current) {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE)
|
||||
ScreenOrientation!.lockAsync(ScreenOrientation!.OrientationLock.LANDSCAPE)
|
||||
.then(() => {
|
||||
orientationLocked.current = true;
|
||||
})
|
||||
|
|
@ -109,8 +130,11 @@ export const usePlayerSetup = (config: PlayerSetupConfig) => {
|
|||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Skip on TV
|
||||
if (!ScreenOrientation) return;
|
||||
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.DEFAULT)
|
||||
.then(() => ScreenOrientation.unlockAsync())
|
||||
.then(() => ScreenOrientation!.unlockAsync())
|
||||
.catch(() => { });
|
||||
};
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import * as ExpoClipboard from 'expo-clipboard';
|
||||
import { View, Text, TouchableOpacity, StyleSheet, useWindowDimensions } from 'react-native';
|
||||
import { View, Text, TouchableOpacity, StyleSheet, useWindowDimensions, Platform } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import Animated, {
|
||||
FadeIn,
|
||||
|
|
@ -9,6 +8,19 @@ import Animated, {
|
|||
ZoomOut,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
// Check if running on TV platform
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import expo-clipboard (not available on TV)
|
||||
let ExpoClipboard: typeof import('expo-clipboard') | null = null;
|
||||
if (!isTV) {
|
||||
try {
|
||||
ExpoClipboard = require('expo-clipboard');
|
||||
} catch (e) {
|
||||
// Silently fail - copy functionality won't be available
|
||||
}
|
||||
}
|
||||
|
||||
interface ErrorModalProps {
|
||||
showErrorModal: boolean;
|
||||
setShowErrorModal: (show: boolean) => void;
|
||||
|
|
@ -34,6 +46,9 @@ export const ErrorModal: React.FC<ErrorModalProps> = ({
|
|||
};
|
||||
|
||||
const handleCopy = async () => {
|
||||
// Skip on TV or if clipboard is not available
|
||||
if (!ExpoClipboard) return;
|
||||
|
||||
await ExpoClipboard.setStringAsync(errorDetails);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { getColors } from 'react-native-image-colors';
|
||||
import type { ImageColorsResult } from 'react-native-image-colors';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
// Check if running on TV platform
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import react-native-image-colors (not available on TV)
|
||||
let getColors: typeof import('react-native-image-colors').getColors | null = null;
|
||||
type ImageColorsResult = import('react-native-image-colors').ImageColorsResult;
|
||||
|
||||
if (!isTV) {
|
||||
try {
|
||||
getColors = require('react-native-image-colors').getColors;
|
||||
} catch (e) {
|
||||
// Silently fail - will use fallback colors
|
||||
}
|
||||
}
|
||||
|
||||
interface DominantColorResult {
|
||||
dominantColor: string | null;
|
||||
|
|
@ -16,11 +30,11 @@ const calculateVibrancy = (hex: string): number => {
|
|||
const r = parseInt(hex.substr(1, 2), 16);
|
||||
const g = parseInt(hex.substr(3, 2), 16);
|
||||
const b = parseInt(hex.substr(5, 2), 16);
|
||||
|
||||
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const saturation = max === 0 ? 0 : (max - min) / max;
|
||||
|
||||
|
||||
return saturation * (max / 255);
|
||||
};
|
||||
|
||||
|
|
@ -29,7 +43,7 @@ const calculateBrightness = (hex: string): number => {
|
|||
const r = parseInt(hex.substr(1, 2), 16);
|
||||
const g = parseInt(hex.substr(3, 2), 16);
|
||||
const b = parseInt(hex.substr(5, 2), 16);
|
||||
|
||||
|
||||
return (r * 299 + g * 587 + b * 114) / 1000;
|
||||
};
|
||||
|
||||
|
|
@ -38,18 +52,18 @@ const darkenColor = (hex: string, factor: number = 0.1): string => {
|
|||
const r = parseInt(hex.substr(1, 2), 16);
|
||||
const g = parseInt(hex.substr(3, 2), 16);
|
||||
const b = parseInt(hex.substr(5, 2), 16);
|
||||
|
||||
|
||||
const newR = Math.floor(r * factor);
|
||||
const newG = Math.floor(g * factor);
|
||||
const newB = Math.floor(b * factor);
|
||||
|
||||
|
||||
return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// Enhanced color selection logic
|
||||
const selectBestColor = (result: ImageColorsResult): string => {
|
||||
let candidates: string[] = [];
|
||||
|
||||
|
||||
if (result.platform === 'android') {
|
||||
// Collect all available colors
|
||||
candidates = [
|
||||
|
|
@ -80,22 +94,22 @@ const selectBestColor = (result: ImageColorsResult): string => {
|
|||
result.lightMuted
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return '#1a1a1a';
|
||||
}
|
||||
|
||||
|
||||
// Score each color based on vibrancy and appropriateness for backgrounds
|
||||
const scoredColors = candidates.map(color => {
|
||||
const brightness = calculateBrightness(color);
|
||||
const vibrancy = calculateVibrancy(color);
|
||||
|
||||
|
||||
// Prefer colors that are:
|
||||
// 1. Not too bright (good for backgrounds)
|
||||
// 2. Have decent vibrancy (not too gray)
|
||||
// 3. Not too dark (still visible)
|
||||
let score = 0;
|
||||
|
||||
|
||||
// Brightness scoring (prefer medium-dark colors)
|
||||
if (brightness >= 30 && brightness <= 120) {
|
||||
score += 3;
|
||||
|
|
@ -104,7 +118,7 @@ const selectBestColor = (result: ImageColorsResult): string => {
|
|||
} else if (brightness >= 5) {
|
||||
score += 1;
|
||||
}
|
||||
|
||||
|
||||
// Vibrancy scoring (prefer some color over pure gray)
|
||||
if (vibrancy >= 0.3) {
|
||||
score += 3;
|
||||
|
|
@ -113,17 +127,17 @@ const selectBestColor = (result: ImageColorsResult): string => {
|
|||
} else if (vibrancy >= 0.05) {
|
||||
score += 1;
|
||||
}
|
||||
|
||||
|
||||
return { color, score, brightness, vibrancy };
|
||||
});
|
||||
|
||||
|
||||
// Sort by score (highest first)
|
||||
scoredColors.sort((a, b) => b.score - a.score);
|
||||
|
||||
|
||||
// Get the best color
|
||||
let bestColor = scoredColors[0].color;
|
||||
const bestBrightness = scoredColors[0].brightness;
|
||||
|
||||
|
||||
// Apply more aggressive darkening to make colors darker overall
|
||||
if (bestBrightness > 60) {
|
||||
bestColor = darkenColor(bestColor, 0.18);
|
||||
|
|
@ -134,16 +148,17 @@ const selectBestColor = (result: ImageColorsResult): string => {
|
|||
} else {
|
||||
bestColor = darkenColor(bestColor, 0.7);
|
||||
}
|
||||
|
||||
|
||||
return bestColor;
|
||||
};
|
||||
|
||||
// Preload function to start extraction early
|
||||
export const preloadDominantColor = async (imageUri: string | null) => {
|
||||
if (!imageUri || colorCache.has(imageUri)) return;
|
||||
|
||||
// Skip on TV or if getColors is not available
|
||||
if (!getColors || !imageUri || colorCache.has(imageUri)) return;
|
||||
|
||||
if (__DEV__) console.log('[useDominantColor] Preloading color for URI:', imageUri);
|
||||
|
||||
|
||||
try {
|
||||
// Use highest quality for best color accuracy
|
||||
const result = await getColors(imageUri, {
|
||||
|
|
@ -201,6 +216,15 @@ export const useDominantColor = (imageUri: string | null): DominantColorResult =
|
|||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Skip color extraction on TV or if getColors is not available
|
||||
if (!getColors) {
|
||||
const fallbackColor = '#1a1a1a';
|
||||
colorCache.set(uri, fallbackColor);
|
||||
safelySetColor(fallbackColor);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use highest quality for best color accuracy
|
||||
const fastResult: ImageColorsResult = await getColors(uri, {
|
||||
fallback: '#1a1a1a',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,19 @@
|
|||
import { useRef, useState } from 'react';
|
||||
import { Animated, Platform } from 'react-native';
|
||||
import { PanGestureHandlerGestureEvent, State } from 'react-native-gesture-handler';
|
||||
import * as Brightness from 'expo-brightness';
|
||||
|
||||
// Check if running on TV platform
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import expo-brightness (not available on TV)
|
||||
let Brightness: typeof import('expo-brightness') | null = null;
|
||||
if (!isTV) {
|
||||
try {
|
||||
Brightness = require('expo-brightness');
|
||||
} catch (e) {
|
||||
// Silently fail - brightness control won't be available
|
||||
}
|
||||
}
|
||||
|
||||
interface GestureControlConfig {
|
||||
volume: number;
|
||||
|
|
@ -19,67 +31,67 @@ export const usePlayerGestureControls = (config: GestureControlConfig) => {
|
|||
// State for overlays
|
||||
const [showVolumeOverlay, setShowVolumeOverlay] = useState(false);
|
||||
const [showBrightnessOverlay, setShowBrightnessOverlay] = useState(false);
|
||||
|
||||
|
||||
// Animated values
|
||||
const volumeGestureTranslateY = useRef(new Animated.Value(0)).current;
|
||||
const brightnessGestureTranslateY = useRef(new Animated.Value(0)).current;
|
||||
const volumeOverlayOpacity = useRef(new Animated.Value(0)).current;
|
||||
const brightnessOverlayOpacity = useRef(new Animated.Value(0)).current;
|
||||
|
||||
|
||||
// Tracking refs
|
||||
const lastVolumeGestureY = useRef(0);
|
||||
const lastBrightnessGestureY = useRef(0);
|
||||
const volumeOverlayTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const brightnessOverlayTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
|
||||
// Extract config with defaults and platform adjustments
|
||||
const volumeRange = config.volumeRange || { min: 0, max: 1 };
|
||||
const baseVolumeSensitivity = config.volumeSensitivity || 0.006;
|
||||
const baseBrightnessSensitivity = config.brightnessSensitivity || 0.004;
|
||||
const overlayTimeout = config.overlayTimeout || 1500;
|
||||
|
||||
|
||||
// Platform-specific sensitivity adjustments
|
||||
// Android needs higher sensitivity due to different touch handling
|
||||
const platformMultiplier = Platform.OS === 'android' ? 1.6 : 1.0;
|
||||
const volumeSensitivity = baseVolumeSensitivity * platformMultiplier;
|
||||
const brightnessSensitivity = baseBrightnessSensitivity * platformMultiplier;
|
||||
|
||||
|
||||
// Volume gesture handler
|
||||
const onVolumeGestureEvent = Animated.event(
|
||||
[{ nativeEvent: { translationY: volumeGestureTranslateY } }],
|
||||
{
|
||||
{
|
||||
useNativeDriver: false,
|
||||
listener: (event: PanGestureHandlerGestureEvent) => {
|
||||
const { translationY, state } = event.nativeEvent;
|
||||
|
||||
|
||||
if (state === State.ACTIVE) {
|
||||
// Auto-initialize on first active frame
|
||||
if (Math.abs(translationY) < 5 && Math.abs(lastVolumeGestureY.current - translationY) > 20) {
|
||||
lastVolumeGestureY.current = translationY;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Calculate delta from last position
|
||||
const deltaY = -(translationY - lastVolumeGestureY.current);
|
||||
lastVolumeGestureY.current = translationY;
|
||||
|
||||
|
||||
// Normalize sensitivity based on volume range
|
||||
const rangeMultiplier = volumeRange.max - volumeRange.min;
|
||||
const volumeChange = deltaY * volumeSensitivity * rangeMultiplier;
|
||||
const newVolume = Math.max(volumeRange.min, Math.min(volumeRange.max, config.volume + volumeChange));
|
||||
|
||||
|
||||
config.setVolume(newVolume);
|
||||
|
||||
|
||||
if (config.debugMode) {
|
||||
console.log(`[GestureControls] Volume set to: ${newVolume} (Platform: ${Platform.OS}, Sensitivity: ${volumeSensitivity})`);
|
||||
}
|
||||
|
||||
|
||||
// Show overlay
|
||||
if (!showVolumeOverlay) {
|
||||
setShowVolumeOverlay(true);
|
||||
volumeOverlayOpacity.setValue(1);
|
||||
}
|
||||
|
||||
|
||||
// Reset hide timer
|
||||
if (volumeOverlayTimeout.current) {
|
||||
clearTimeout(volumeOverlayTimeout.current);
|
||||
|
|
@ -95,40 +107,41 @@ export const usePlayerGestureControls = (config: GestureControlConfig) => {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Brightness gesture handler
|
||||
const onBrightnessGestureEvent = Animated.event(
|
||||
[{ nativeEvent: { translationY: brightnessGestureTranslateY } }],
|
||||
{
|
||||
{
|
||||
useNativeDriver: false,
|
||||
listener: (event: PanGestureHandlerGestureEvent) => {
|
||||
const { translationY, state } = event.nativeEvent;
|
||||
|
||||
|
||||
if (state === State.ACTIVE) {
|
||||
// Auto-initialize
|
||||
if (Math.abs(translationY) < 5 && Math.abs(lastBrightnessGestureY.current - translationY) > 20) {
|
||||
lastBrightnessGestureY.current = translationY;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const deltaY = -(translationY - lastBrightnessGestureY.current);
|
||||
lastBrightnessGestureY.current = translationY;
|
||||
|
||||
|
||||
const brightnessChange = deltaY * brightnessSensitivity;
|
||||
const newBrightness = Math.max(0, Math.min(1, config.brightness + brightnessChange));
|
||||
|
||||
|
||||
config.setBrightness(newBrightness);
|
||||
Brightness.setBrightnessAsync(newBrightness).catch(() => {});
|
||||
|
||||
// Only set device brightness if available (not on TV)
|
||||
Brightness?.setBrightnessAsync(newBrightness).catch(() => { });
|
||||
|
||||
if (config.debugMode) {
|
||||
console.log(`[GestureControls] Device brightness set to: ${newBrightness} (Platform: ${Platform.OS}, Sensitivity: ${brightnessSensitivity})`);
|
||||
}
|
||||
|
||||
|
||||
if (!showBrightnessOverlay) {
|
||||
setShowBrightnessOverlay(true);
|
||||
brightnessOverlayOpacity.setValue(1);
|
||||
}
|
||||
|
||||
|
||||
if (brightnessOverlayTimeout.current) {
|
||||
clearTimeout(brightnessOverlayTimeout.current);
|
||||
}
|
||||
|
|
@ -143,7 +156,7 @@ export const usePlayerGestureControls = (config: GestureControlConfig) => {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Cleanup function
|
||||
const cleanup = () => {
|
||||
if (volumeOverlayTimeout.current) {
|
||||
|
|
@ -153,18 +166,18 @@ export const usePlayerGestureControls = (config: GestureControlConfig) => {
|
|||
clearTimeout(brightnessOverlayTimeout.current);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
// Gesture handlers
|
||||
onVolumeGestureEvent,
|
||||
onBrightnessGestureEvent,
|
||||
|
||||
|
||||
// Overlay state
|
||||
showVolumeOverlay,
|
||||
showBrightnessOverlay,
|
||||
volumeOverlayOpacity,
|
||||
brightnessOverlayOpacity,
|
||||
|
||||
|
||||
// Cleanup
|
||||
cleanup,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -58,9 +58,21 @@ import homeStyles, { sharedStyles } from '../styles/homeStyles';
|
|||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import type { Theme } from '../contexts/ThemeContext';
|
||||
import { useLoading } from '../contexts/LoadingContext';
|
||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
// Check if running on TV platform
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import ScreenOrientation (not available on TV)
|
||||
let ScreenOrientation: typeof import('expo-screen-orientation') | null = null;
|
||||
if (!isTV) {
|
||||
try {
|
||||
ScreenOrientation = require('expo-screen-orientation');
|
||||
} catch (e) {
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
import FirstTimeWelcome from '../components/FirstTimeWelcome';
|
||||
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
||||
|
|
@ -445,8 +457,8 @@ const HomeScreen = () => {
|
|||
|
||||
statusBarConfig();
|
||||
|
||||
// Unlock orientation to allow free rotation
|
||||
ScreenOrientation.unlockAsync().catch(() => { });
|
||||
// Unlock orientation to allow free rotation (skip on TV)
|
||||
ScreenOrientation?.unlockAsync().catch(() => { });
|
||||
|
||||
return () => {
|
||||
// Stop trailer when screen loses focus (navigating to other screens)
|
||||
|
|
@ -537,9 +549,11 @@ const HomeScreen = () => {
|
|||
// Don't clear cache before player - causes broken images on return
|
||||
// FastImage's native libraries handle memory efficiently
|
||||
|
||||
// Lock orientation to landscape before navigation to prevent glitches
|
||||
// Lock orientation to landscape before navigation to prevent glitches (skip on TV)
|
||||
try {
|
||||
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
|
||||
if (ScreenOrientation) {
|
||||
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
|
||||
}
|
||||
|
||||
// Longer delay to ensure orientation is fully set before navigation
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
|
|
|||
|
|
@ -29,8 +29,24 @@ import { useTraktContext } from '../contexts/TraktContext';
|
|||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { catalogService } from '../services/catalogService';
|
||||
import { fetchTotalDownloads } from '../services/githubReleaseService';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
// Check if running on TV
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Lazy load WebBrowser to avoid native module errors on TV
|
||||
let _webBrowser: typeof import('expo-web-browser') | null = null;
|
||||
const getWebBrowser = () => {
|
||||
if (_webBrowser !== null) return _webBrowser;
|
||||
if (isTV) return null;
|
||||
try {
|
||||
_webBrowser = require('expo-web-browser');
|
||||
return _webBrowser;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
import * as Sentry from '@sentry/react-native';
|
||||
import { getDisplayedAppVersion } from '../utils/version';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
|
@ -945,9 +961,17 @@ const SettingsScreen: React.FC = () => {
|
|||
<View style={styles.discordContainer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.discordButton, { backgroundColor: 'transparent', paddingVertical: 0, paddingHorizontal: 0, marginBottom: 8 }]}
|
||||
onPress={() => WebBrowser.openBrowserAsync('https://ko-fi.com/tapframe', {
|
||||
presentationStyle: Platform.OS === 'ios' ? WebBrowser.WebBrowserPresentationStyle.FORM_SHEET : WebBrowser.WebBrowserPresentationStyle.FORM_SHEET
|
||||
})}
|
||||
onPress={() => {
|
||||
const url = 'https://ko-fi.com/tapframe';
|
||||
const wb = getWebBrowser();
|
||||
if (wb) {
|
||||
wb.openBrowserAsync(url, {
|
||||
presentationStyle: Platform.OS === 'ios' ? wb.WebBrowserPresentationStyle.FORM_SHEET : wb.WebBrowserPresentationStyle.FORM_SHEET
|
||||
});
|
||||
} else {
|
||||
Linking.openURL(url);
|
||||
}
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<FastImage
|
||||
|
|
@ -1071,9 +1095,17 @@ const SettingsScreen: React.FC = () => {
|
|||
<View style={styles.discordContainer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.discordButton, { backgroundColor: 'transparent', paddingVertical: 0, paddingHorizontal: 0, marginBottom: 8 }]}
|
||||
onPress={() => WebBrowser.openBrowserAsync('https://ko-fi.com/tapframe', {
|
||||
presentationStyle: Platform.OS === 'ios' ? WebBrowser.WebBrowserPresentationStyle.FORM_SHEET : WebBrowser.WebBrowserPresentationStyle.FORM_SHEET
|
||||
})}
|
||||
onPress={() => {
|
||||
const url = 'https://ko-fi.com/tapframe';
|
||||
const wb = getWebBrowser();
|
||||
if (wb) {
|
||||
wb.openBrowserAsync(url, {
|
||||
presentationStyle: Platform.OS === 'ios' ? wb.WebBrowserPresentationStyle.FORM_SHEET : wb.WebBrowserPresentationStyle.FORM_SHEET
|
||||
});
|
||||
} else {
|
||||
Linking.openURL(url);
|
||||
}
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<FastImage
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ import Animated, {
|
|||
runOnJS
|
||||
} from 'react-native-reanimated';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { useRoute, useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { NavigationProp } from '@react-navigation/native';
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
Switch,
|
||||
} from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { makeRedirectUri, useAuthRequest, ResponseType, Prompt, CodeChallengeMethod } from 'expo-auth-session';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import { traktService, TraktUser } from '../services/traktService';
|
||||
|
|
@ -26,6 +25,28 @@ import { useTraktAutosyncSettings } from '../hooks/useTraktAutosyncSettings';
|
|||
import { colors } from '../styles';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
// Check if running on TV platform
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import expo-auth-session (not available on TV)
|
||||
let makeRedirectUri: typeof import('expo-auth-session').makeRedirectUri | null = null;
|
||||
let useAuthRequest: typeof import('expo-auth-session').useAuthRequest | null = null;
|
||||
let ResponseType: typeof import('expo-auth-session').ResponseType | null = null;
|
||||
let CodeChallengeMethod: typeof import('expo-auth-session').CodeChallengeMethod | null = null;
|
||||
|
||||
if (!isTV) {
|
||||
try {
|
||||
const authSession = require('expo-auth-session');
|
||||
makeRedirectUri = authSession.makeRedirectUri;
|
||||
useAuthRequest = authSession.useAuthRequest;
|
||||
ResponseType = authSession.ResponseType;
|
||||
CodeChallengeMethod = authSession.CodeChallengeMethod;
|
||||
} catch (e) {
|
||||
// Silently fail - auth won't be available on TV
|
||||
logger.warn('[TraktSettingsScreen] expo-auth-session not available');
|
||||
}
|
||||
}
|
||||
|
||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||
|
||||
// Trakt configuration
|
||||
|
|
@ -39,11 +60,11 @@ const discovery = {
|
|||
tokenEndpoint: 'https://api.trakt.tv/oauth/token',
|
||||
};
|
||||
|
||||
// For use with deep linking
|
||||
const redirectUri = makeRedirectUri({
|
||||
// For use with deep linking (only on non-TV platforms)
|
||||
const redirectUri = makeRedirectUri?.({
|
||||
scheme: 'nuvio',
|
||||
path: 'auth/trakt',
|
||||
});
|
||||
}) || 'nuvio://auth/trakt';
|
||||
|
||||
const TraktSettingsScreen: React.FC = () => {
|
||||
const { settings, updateSetting } = useSettings();
|
||||
|
|
@ -53,7 +74,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [userProfile, setUserProfile] = useState<TraktUser | null>(null);
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
|
||||
const {
|
||||
settings: autosyncSettings,
|
||||
isSyncing,
|
||||
|
|
@ -101,7 +122,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
try {
|
||||
const authenticated = await traktService.isAuthenticated();
|
||||
setIsAuthenticated(authenticated);
|
||||
|
||||
|
||||
if (authenticated) {
|
||||
const profile = await traktService.getUserProfile();
|
||||
setUserProfile(profile);
|
||||
|
|
@ -119,18 +140,23 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
checkAuthStatus();
|
||||
}, [checkAuthStatus]);
|
||||
|
||||
// Setup expo-auth-session hook with PKCE
|
||||
const [request, response, promptAsync] = useAuthRequest(
|
||||
{
|
||||
clientId: TRAKT_CLIENT_ID,
|
||||
scopes: [],
|
||||
redirectUri: redirectUri,
|
||||
responseType: ResponseType.Code,
|
||||
usePKCE: true,
|
||||
codeChallengeMethod: CodeChallengeMethod.S256,
|
||||
},
|
||||
discovery
|
||||
);
|
||||
// Setup expo-auth-session hook with PKCE (only on non-TV platforms)
|
||||
// On TV, we'll return null values and disable auth
|
||||
const authResult = useAuthRequest && ResponseType && CodeChallengeMethod
|
||||
? useAuthRequest(
|
||||
{
|
||||
clientId: TRAKT_CLIENT_ID,
|
||||
scopes: [],
|
||||
redirectUri: redirectUri,
|
||||
responseType: ResponseType.Code,
|
||||
usePKCE: true,
|
||||
codeChallengeMethod: CodeChallengeMethod.S256,
|
||||
},
|
||||
discovery
|
||||
)
|
||||
: [null, null, async () => ({ type: 'dismiss' as const })];
|
||||
|
||||
const [request, response, promptAsync] = authResult as any;
|
||||
|
||||
const [isExchangingCode, setIsExchangingCode] = useState(false);
|
||||
|
||||
|
|
@ -151,8 +177,8 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
'Successfully Connected',
|
||||
'Your Trakt account has been connected successfully.',
|
||||
[
|
||||
{
|
||||
label: 'OK',
|
||||
{
|
||||
label: 'OK',
|
||||
onPress: () => navigation.goBack(),
|
||||
}
|
||||
]
|
||||
|
|
@ -190,9 +216,9 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
'Sign Out',
|
||||
'Are you sure you want to sign out of your Trakt account?',
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{
|
||||
label: 'Sign Out',
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Sign Out',
|
||||
onPress: async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
|
|
@ -224,26 +250,26 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
onPress={() => navigation.goBack()}
|
||||
style={styles.backButton}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="arrow-back"
|
||||
size={24}
|
||||
color={isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark}
|
||||
<MaterialIcons
|
||||
name="arrow-back"
|
||||
size={24}
|
||||
color={isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark}
|
||||
/>
|
||||
<Text style={[styles.backText, { color: isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark }]}>
|
||||
Settings
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
<View style={styles.headerActions}>
|
||||
{/* Empty for now, but ready for future actions */}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
<Text style={[styles.headerTitle, { color: isDarkMode ? currentTheme.colors.highEmphasis : currentTheme.colors.textDark }]}>
|
||||
Trakt Settings
|
||||
</Text>
|
||||
|
||||
<ScrollView
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
|
|
@ -259,8 +285,8 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
<View style={styles.profileContainer}>
|
||||
<View style={styles.profileHeader}>
|
||||
{userProfile.avatar ? (
|
||||
<FastImage
|
||||
source={{ uri: userProfile.avatar }}
|
||||
<FastImage
|
||||
source={{ uri: userProfile.avatar }}
|
||||
style={styles.avatar}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
|
|
@ -315,7 +341,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
</View>
|
||||
) : (
|
||||
<View style={styles.signInContainer}>
|
||||
<TraktIcon
|
||||
<TraktIcon
|
||||
width={120}
|
||||
height={120}
|
||||
style={styles.traktLogo}
|
||||
|
|
@ -497,7 +523,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
|
||||
<CustomAlert
|
||||
visible={alertVisible}
|
||||
title={alertTitle}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import * as Notifications from 'expo-notifications';
|
||||
import { Platform, AppState, AppStateStatus } from 'react-native';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { parseISO, differenceInHours, isToday, addDays, isAfter, startOfToday } from 'date-fns';
|
||||
|
|
@ -9,13 +8,28 @@ import { tmdbService } from './tmdbService';
|
|||
import { logger } from '../utils/logger';
|
||||
import { memoryManager } from '../utils/memoryManager';
|
||||
|
||||
// Check if running on TV platform (tvOS or Android TV)
|
||||
const isTV = Platform.isTV;
|
||||
|
||||
// Conditionally import expo-notifications only on non-TV platforms
|
||||
// This prevents the native module error on TV
|
||||
let Notifications: typeof import('expo-notifications') | null = null;
|
||||
let SchedulableTriggerInputTypes: any = null;
|
||||
|
||||
if (!isTV) {
|
||||
try {
|
||||
// Dynamic require to avoid loading on TV
|
||||
Notifications = require('expo-notifications');
|
||||
SchedulableTriggerInputTypes = Notifications?.SchedulableTriggerInputTypes;
|
||||
} catch (e) {
|
||||
logger.warn('[NotificationService] expo-notifications not available:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Define notification storage keys
|
||||
const NOTIFICATION_STORAGE_KEY = 'stremio-notifications';
|
||||
const NOTIFICATION_SETTINGS_KEY = 'stremio-notification-settings';
|
||||
|
||||
// Import the correct type from Notifications
|
||||
const { SchedulableTriggerInputTypes } = Notifications;
|
||||
|
||||
// Notification settings interface
|
||||
export interface NotificationSettings {
|
||||
enabled: boolean;
|
||||
|
|
@ -27,10 +41,10 @@ export interface NotificationSettings {
|
|||
|
||||
// Default notification settings
|
||||
const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings = {
|
||||
enabled: true,
|
||||
newEpisodeNotifications: true,
|
||||
reminderNotifications: true,
|
||||
upcomingShowsNotifications: true,
|
||||
enabled: !isTV, // Disable by default on TV
|
||||
newEpisodeNotifications: !isTV,
|
||||
reminderNotifications: !isTV,
|
||||
upcomingShowsNotifications: !isTV,
|
||||
timeBeforeAiring: 24, // 24 hours before airing
|
||||
};
|
||||
|
||||
|
|
@ -60,6 +74,12 @@ class NotificationService {
|
|||
private lastDownloadNotificationTime: Map<string, number> = new Map();
|
||||
|
||||
private constructor() {
|
||||
// Skip notification initialization on TV platforms
|
||||
if (isTV) {
|
||||
logger.log('[NotificationService] Notifications disabled on TV platform');
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize notifications
|
||||
this.configureNotifications();
|
||||
this.loadSettings();
|
||||
|
|
@ -77,6 +97,9 @@ class NotificationService {
|
|||
}
|
||||
|
||||
private async configureNotifications() {
|
||||
// Skip on TV platforms or if Notifications not available
|
||||
if (isTV || !Notifications) return;
|
||||
|
||||
// Configure notification behavior
|
||||
await Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
|
|
@ -152,6 +175,9 @@ class NotificationService {
|
|||
}
|
||||
|
||||
async scheduleEpisodeNotification(item: NotificationItem): Promise<string | null> {
|
||||
// Skip on TV platforms
|
||||
if (isTV) return null;
|
||||
|
||||
if (!this.settings.enabled || !this.settings.newEpisodeNotifications) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -185,7 +211,7 @@ class NotificationService {
|
|||
}
|
||||
|
||||
// Schedule the notification
|
||||
const notificationId = await Notifications.scheduleNotificationAsync({
|
||||
const notificationId = await Notifications!.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: `New Episode: ${item.seriesName}`,
|
||||
body: `S${item.season}:E${item.episode} - ${item.episodeTitle} is airing soon!`,
|
||||
|
|
@ -196,7 +222,7 @@ class NotificationService {
|
|||
},
|
||||
trigger: {
|
||||
date: notificationTime,
|
||||
type: SchedulableTriggerInputTypes.DATE,
|
||||
type: SchedulableTriggerInputTypes?.DATE,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
75
src/utils/tvPlatform.ts
Normal file
75
src/utils/tvPlatform.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* TV Platform Utilities
|
||||
*
|
||||
* This module provides utilities for handling TV-specific behavior
|
||||
* on tvOS and Android TV platforms.
|
||||
*/
|
||||
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
/**
|
||||
* Check if the app is running on a TV platform (tvOS or Android TV)
|
||||
*/
|
||||
export const isTV = Platform.isTV;
|
||||
|
||||
/**
|
||||
* Check if the app is running on tvOS specifically
|
||||
*/
|
||||
export const isTVOS = Platform.OS === 'ios' && Platform.isTV;
|
||||
|
||||
/**
|
||||
* Check if the app is running on Android TV specifically
|
||||
*/
|
||||
export const isAndroidTV = Platform.OS === 'android' && Platform.isTV;
|
||||
|
||||
/**
|
||||
* Features that are NOT supported on TV platforms
|
||||
*/
|
||||
export const unsupportedTVFeatures = {
|
||||
// Push notifications are not available on TV
|
||||
pushNotifications: true,
|
||||
// Haptic feedback doesn't exist on TV
|
||||
haptics: true,
|
||||
// Brightness control doesn't make sense on TV
|
||||
brightnessControl: true,
|
||||
// Casting from TV doesn't make sense (you're already on TV)
|
||||
casting: true,
|
||||
// Device orientation doesn't apply to TV
|
||||
orientationLock: true,
|
||||
// Touch gestures need to be adapted for D-pad/remote
|
||||
touchGestures: true,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Safely execute a function only on non-TV platforms
|
||||
* Returns undefined if on TV, otherwise returns the function result
|
||||
*/
|
||||
export function runIfNotTV<T>(fn: () => T): T | undefined {
|
||||
if (isTV) return undefined;
|
||||
return fn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely execute an async function only on non-TV platforms
|
||||
* Returns undefined if on TV, otherwise returns the awaited result
|
||||
*/
|
||||
export async function runIfNotTVAsync<T>(fn: () => Promise<T>): Promise<T | undefined> {
|
||||
if (isTV) return undefined;
|
||||
return fn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a TV-safe value, returning the fallback on TV platforms
|
||||
*/
|
||||
export function tvSafeValue<T>(value: T, tvFallback: T): T {
|
||||
return isTV ? tvFallback : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log messages about TV platform limitations (development only)
|
||||
*/
|
||||
export function logTVUnsupported(feature: string): void {
|
||||
if (__DEV__ && isTV) {
|
||||
console.log(`[TV] Feature "${feature}" is not supported on TV platforms`);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue