diff --git a/app.json b/app.json index fab763e..ec82ab8 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,7 @@ { "expo": { - "name": "Nuvio", - "slug": "nuvio", + "name": "Stremio Expo", + "slug": "stremio-expo", "version": "1.0.0", "orientation": "default", "icon": "./assets/icon.png", @@ -18,7 +18,8 @@ "NSAppTransportSecurity": { "NSAllowsArbitraryLoads": true } - } + }, + "bundleIdentifier": "com.nuvio.app" }, "android": { "adaptiveIcon": { diff --git a/build/config.gypi b/build/config.gypi new file mode 100644 index 0000000..27a147f --- /dev/null +++ b/build/config.gypi @@ -0,0 +1,466 @@ +# Do not edit. File was generated by node-gyp's "configure" step +{ + "target_defaults": { + "cflags": [], + "configurations": { + "Debug": { + "v8_enable_v8_checks": 0, + "variables": {} + }, + "Release": { + "v8_enable_v8_checks": 1, + "variables": {} + } + }, + "default_configuration": "Release", + "defines": [], + "include_dirs": [], + "libraries": [], + "msbuild_toolset": "v143", + "msvs_windows_target_platform_version": "10.0.22621.0" + }, + "variables": { + "asan": 0, + "clang": 0, + "coverage": "false", + "dcheck_always_on": 0, + "debug_nghttp2": "false", + "debug_node": "false", + "enable_lto": "false", + "enable_pgo_generate": "false", + "enable_pgo_use": "false", + "error_on_warn": "false", + "force_dynamic_crt": 0, + "host_arch": "x64", + "icu_data_in": "..\\..\\deps\\icu-tmp\\icudt76l.dat", + "icu_endianness": "l", + "icu_gyp_path": "tools/icu/icu-generic.gyp", + "icu_path": "deps/icu-small", + "icu_small": "false", + "icu_ver_major": "76", + "libdir": "lib", + "llvm_version": "0.0", + "napi_build_version": "9", + "nasm_version": "2.16", + "node_builtin_shareable_builtins": [ + "deps/cjs-module-lexer/lexer.js", + "deps/cjs-module-lexer/dist/lexer.js", + "deps/undici/undici.js", + "deps/amaro/dist/index.js" + ], + "node_byteorder": "little", + "node_debug_lib": "false", + "node_enable_d8": "false", + "node_enable_v8_vtunejit": "false", + "node_fipsinstall": "false", + "node_install_corepack": "true", + "node_install_npm": "true", + "node_library_files": [ + "lib/_http_agent.js", + "lib/_http_client.js", + "lib/_http_common.js", + "lib/_http_incoming.js", + "lib/_http_outgoing.js", + "lib/_http_server.js", + "lib/_stream_duplex.js", + "lib/_stream_passthrough.js", + "lib/_stream_readable.js", + "lib/_stream_transform.js", + "lib/_stream_wrap.js", + "lib/_stream_writable.js", + "lib/_tls_common.js", + "lib/_tls_wrap.js", + "lib/assert.js", + "lib/assert/strict.js", + "lib/async_hooks.js", + "lib/buffer.js", + "lib/child_process.js", + "lib/cluster.js", + "lib/console.js", + "lib/constants.js", + "lib/crypto.js", + "lib/dgram.js", + "lib/diagnostics_channel.js", + "lib/dns.js", + "lib/dns/promises.js", + "lib/domain.js", + "lib/events.js", + "lib/fs.js", + "lib/fs/promises.js", + "lib/http.js", + "lib/http2.js", + "lib/https.js", + "lib/inspector.js", + "lib/inspector/promises.js", + "lib/internal/abort_controller.js", + "lib/internal/assert.js", + "lib/internal/assert/assertion_error.js", + "lib/internal/assert/calltracker.js", + "lib/internal/assert/myers_diff.js", + "lib/internal/assert/utils.js", + "lib/internal/async_context_frame.js", + "lib/internal/async_hooks.js", + "lib/internal/async_local_storage/async_context_frame.js", + "lib/internal/async_local_storage/async_hooks.js", + "lib/internal/blob.js", + "lib/internal/blocklist.js", + "lib/internal/bootstrap/node.js", + "lib/internal/bootstrap/realm.js", + "lib/internal/bootstrap/shadow_realm.js", + "lib/internal/bootstrap/switches/does_not_own_process_state.js", + "lib/internal/bootstrap/switches/does_own_process_state.js", + "lib/internal/bootstrap/switches/is_main_thread.js", + "lib/internal/bootstrap/switches/is_not_main_thread.js", + "lib/internal/bootstrap/web/exposed-wildcard.js", + "lib/internal/bootstrap/web/exposed-window-or-worker.js", + "lib/internal/buffer.js", + "lib/internal/child_process.js", + "lib/internal/child_process/serialization.js", + "lib/internal/cli_table.js", + "lib/internal/cluster/child.js", + "lib/internal/cluster/primary.js", + "lib/internal/cluster/round_robin_handle.js", + "lib/internal/cluster/shared_handle.js", + "lib/internal/cluster/utils.js", + "lib/internal/cluster/worker.js", + "lib/internal/console/constructor.js", + "lib/internal/console/global.js", + "lib/internal/constants.js", + "lib/internal/crypto/aes.js", + "lib/internal/crypto/certificate.js", + "lib/internal/crypto/cfrg.js", + "lib/internal/crypto/cipher.js", + "lib/internal/crypto/diffiehellman.js", + "lib/internal/crypto/ec.js", + "lib/internal/crypto/hash.js", + "lib/internal/crypto/hashnames.js", + "lib/internal/crypto/hkdf.js", + "lib/internal/crypto/keygen.js", + "lib/internal/crypto/keys.js", + "lib/internal/crypto/mac.js", + "lib/internal/crypto/pbkdf2.js", + "lib/internal/crypto/random.js", + "lib/internal/crypto/rsa.js", + "lib/internal/crypto/scrypt.js", + "lib/internal/crypto/sig.js", + "lib/internal/crypto/util.js", + "lib/internal/crypto/webcrypto.js", + "lib/internal/crypto/webidl.js", + "lib/internal/crypto/x509.js", + "lib/internal/data_url.js", + "lib/internal/debugger/inspect.js", + "lib/internal/debugger/inspect_client.js", + "lib/internal/debugger/inspect_repl.js", + "lib/internal/dgram.js", + "lib/internal/dns/callback_resolver.js", + "lib/internal/dns/promises.js", + "lib/internal/dns/utils.js", + "lib/internal/encoding.js", + "lib/internal/error_serdes.js", + "lib/internal/errors.js", + "lib/internal/event_target.js", + "lib/internal/events/abort_listener.js", + "lib/internal/events/symbols.js", + "lib/internal/file.js", + "lib/internal/fixed_queue.js", + "lib/internal/freelist.js", + "lib/internal/freeze_intrinsics.js", + "lib/internal/fs/cp/cp-sync.js", + "lib/internal/fs/cp/cp.js", + "lib/internal/fs/dir.js", + "lib/internal/fs/glob.js", + "lib/internal/fs/promises.js", + "lib/internal/fs/read/context.js", + "lib/internal/fs/recursive_watch.js", + "lib/internal/fs/rimraf.js", + "lib/internal/fs/streams.js", + "lib/internal/fs/sync_write_stream.js", + "lib/internal/fs/utils.js", + "lib/internal/fs/watchers.js", + "lib/internal/heap_utils.js", + "lib/internal/histogram.js", + "lib/internal/http.js", + "lib/internal/http2/compat.js", + "lib/internal/http2/core.js", + "lib/internal/http2/util.js", + "lib/internal/inspector_async_hook.js", + "lib/internal/inspector_network_tracking.js", + "lib/internal/js_stream_socket.js", + "lib/internal/legacy/processbinding.js", + "lib/internal/linkedlist.js", + "lib/internal/main/check_syntax.js", + "lib/internal/main/embedding.js", + "lib/internal/main/eval_stdin.js", + "lib/internal/main/eval_string.js", + "lib/internal/main/inspect.js", + "lib/internal/main/mksnapshot.js", + "lib/internal/main/print_help.js", + "lib/internal/main/prof_process.js", + "lib/internal/main/repl.js", + "lib/internal/main/run_main_module.js", + "lib/internal/main/test_runner.js", + "lib/internal/main/watch_mode.js", + "lib/internal/main/worker_thread.js", + "lib/internal/mime.js", + "lib/internal/modules/cjs/loader.js", + "lib/internal/modules/esm/assert.js", + "lib/internal/modules/esm/create_dynamic_module.js", + "lib/internal/modules/esm/fetch_module.js", + "lib/internal/modules/esm/formats.js", + "lib/internal/modules/esm/get_format.js", + "lib/internal/modules/esm/hooks.js", + "lib/internal/modules/esm/initialize_import_meta.js", + "lib/internal/modules/esm/load.js", + "lib/internal/modules/esm/loader.js", + "lib/internal/modules/esm/module_job.js", + "lib/internal/modules/esm/module_map.js", + "lib/internal/modules/esm/resolve.js", + "lib/internal/modules/esm/shared_constants.js", + "lib/internal/modules/esm/translators.js", + "lib/internal/modules/esm/utils.js", + "lib/internal/modules/esm/worker.js", + "lib/internal/modules/helpers.js", + "lib/internal/modules/package_json_reader.js", + "lib/internal/modules/run_main.js", + "lib/internal/modules/typescript.js", + "lib/internal/navigator.js", + "lib/internal/net.js", + "lib/internal/options.js", + "lib/internal/per_context/domexception.js", + "lib/internal/per_context/messageport.js", + "lib/internal/per_context/primordials.js", + "lib/internal/perf/event_loop_delay.js", + "lib/internal/perf/event_loop_utilization.js", + "lib/internal/perf/nodetiming.js", + "lib/internal/perf/observe.js", + "lib/internal/perf/performance.js", + "lib/internal/perf/performance_entry.js", + "lib/internal/perf/resource_timing.js", + "lib/internal/perf/timerify.js", + "lib/internal/perf/usertiming.js", + "lib/internal/perf/utils.js", + "lib/internal/priority_queue.js", + "lib/internal/process/execution.js", + "lib/internal/process/finalization.js", + "lib/internal/process/per_thread.js", + "lib/internal/process/permission.js", + "lib/internal/process/pre_execution.js", + "lib/internal/process/promises.js", + "lib/internal/process/report.js", + "lib/internal/process/signal.js", + "lib/internal/process/task_queues.js", + "lib/internal/process/warning.js", + "lib/internal/process/worker_thread_only.js", + "lib/internal/promise_hooks.js", + "lib/internal/querystring.js", + "lib/internal/quic/quic.js", + "lib/internal/quic/state.js", + "lib/internal/quic/stats.js", + "lib/internal/quic/symbols.js", + "lib/internal/readline/callbacks.js", + "lib/internal/readline/emitKeypressEvents.js", + "lib/internal/readline/interface.js", + "lib/internal/readline/promises.js", + "lib/internal/readline/utils.js", + "lib/internal/repl.js", + "lib/internal/repl/await.js", + "lib/internal/repl/history.js", + "lib/internal/repl/utils.js", + "lib/internal/socket_list.js", + "lib/internal/socketaddress.js", + "lib/internal/source_map/prepare_stack_trace.js", + "lib/internal/source_map/source_map.js", + "lib/internal/source_map/source_map_cache.js", + "lib/internal/source_map/source_map_cache_map.js", + "lib/internal/stream_base_commons.js", + "lib/internal/streams/add-abort-signal.js", + "lib/internal/streams/compose.js", + "lib/internal/streams/destroy.js", + "lib/internal/streams/duplex.js", + "lib/internal/streams/duplexify.js", + "lib/internal/streams/duplexpair.js", + "lib/internal/streams/end-of-stream.js", + "lib/internal/streams/from.js", + "lib/internal/streams/lazy_transform.js", + "lib/internal/streams/legacy.js", + "lib/internal/streams/operators.js", + "lib/internal/streams/passthrough.js", + "lib/internal/streams/pipeline.js", + "lib/internal/streams/readable.js", + "lib/internal/streams/state.js", + "lib/internal/streams/transform.js", + "lib/internal/streams/utils.js", + "lib/internal/streams/writable.js", + "lib/internal/test/binding.js", + "lib/internal/test/transfer.js", + "lib/internal/test_runner/coverage.js", + "lib/internal/test_runner/harness.js", + "lib/internal/test_runner/mock/loader.js", + "lib/internal/test_runner/mock/mock.js", + "lib/internal/test_runner/mock/mock_timers.js", + "lib/internal/test_runner/reporter/dot.js", + "lib/internal/test_runner/reporter/junit.js", + "lib/internal/test_runner/reporter/lcov.js", + "lib/internal/test_runner/reporter/spec.js", + "lib/internal/test_runner/reporter/tap.js", + "lib/internal/test_runner/reporter/utils.js", + "lib/internal/test_runner/reporter/v8-serializer.js", + "lib/internal/test_runner/runner.js", + "lib/internal/test_runner/snapshot.js", + "lib/internal/test_runner/test.js", + "lib/internal/test_runner/tests_stream.js", + "lib/internal/test_runner/utils.js", + "lib/internal/timers.js", + "lib/internal/tls/secure-context.js", + "lib/internal/tls/secure-pair.js", + "lib/internal/trace_events_async_hooks.js", + "lib/internal/tty.js", + "lib/internal/url.js", + "lib/internal/util.js", + "lib/internal/util/colors.js", + "lib/internal/util/comparisons.js", + "lib/internal/util/debuglog.js", + "lib/internal/util/inspect.js", + "lib/internal/util/inspector.js", + "lib/internal/util/parse_args/parse_args.js", + "lib/internal/util/parse_args/utils.js", + "lib/internal/util/types.js", + "lib/internal/v8/startup_snapshot.js", + "lib/internal/v8_prof_polyfill.js", + "lib/internal/v8_prof_processor.js", + "lib/internal/validators.js", + "lib/internal/vm.js", + "lib/internal/vm/module.js", + "lib/internal/wasm_web_api.js", + "lib/internal/watch_mode/files_watcher.js", + "lib/internal/watchdog.js", + "lib/internal/webidl.js", + "lib/internal/webstorage.js", + "lib/internal/webstreams/adapters.js", + "lib/internal/webstreams/compression.js", + "lib/internal/webstreams/encoding.js", + "lib/internal/webstreams/queuingstrategies.js", + "lib/internal/webstreams/readablestream.js", + "lib/internal/webstreams/transfer.js", + "lib/internal/webstreams/transformstream.js", + "lib/internal/webstreams/util.js", + "lib/internal/webstreams/writablestream.js", + "lib/internal/worker.js", + "lib/internal/worker/io.js", + "lib/internal/worker/js_transferable.js", + "lib/internal/worker/messaging.js", + "lib/module.js", + "lib/net.js", + "lib/os.js", + "lib/path.js", + "lib/path/posix.js", + "lib/path/win32.js", + "lib/perf_hooks.js", + "lib/process.js", + "lib/punycode.js", + "lib/querystring.js", + "lib/readline.js", + "lib/readline/promises.js", + "lib/repl.js", + "lib/sea.js", + "lib/sqlite.js", + "lib/stream.js", + "lib/stream/consumers.js", + "lib/stream/promises.js", + "lib/stream/web.js", + "lib/string_decoder.js", + "lib/sys.js", + "lib/test.js", + "lib/test/reporters.js", + "lib/timers.js", + "lib/timers/promises.js", + "lib/tls.js", + "lib/trace_events.js", + "lib/tty.js", + "lib/url.js", + "lib/util.js", + "lib/util/types.js", + "lib/v8.js", + "lib/vm.js", + "lib/wasi.js", + "lib/worker_threads.js", + "lib/zlib.js" + ], + "node_module_version": 127, + "node_no_browser_globals": "false", + "node_prefix": "\\usr\\local", + "node_release_urlbase": "https://nodejs.org/download/release/", + "node_shared": "false", + "node_shared_ada": "false", + "node_shared_brotli": "false", + "node_shared_cares": "false", + "node_shared_http_parser": "false", + "node_shared_libuv": "false", + "node_shared_nghttp2": "false", + "node_shared_nghttp3": "false", + "node_shared_ngtcp2": "false", + "node_shared_openssl": "false", + "node_shared_simdjson": "false", + "node_shared_simdutf": "false", + "node_shared_sqlite": "false", + "node_shared_uvwasi": "false", + "node_shared_zlib": "false", + "node_tag": "", + "node_target_type": "executable", + "node_use_amaro": "true", + "node_use_bundled_v8": "true", + "node_use_node_code_cache": "true", + "node_use_node_snapshot": "true", + "node_use_openssl": "true", + "node_use_v8_platform": "true", + "node_with_ltcg": "true", + "node_without_node_options": "false", + "node_write_snapshot_as_array_literals": "true", + "openssl_is_fips": "false", + "openssl_quic": "true", + "ossfuzz": "false", + "shlib_suffix": "so.127", + "single_executable_application": "true", + "target_arch": "x64", + "ubsan": 0, + "use_prefix_to_find_headers": "false", + "v8_enable_31bit_smis_on_64bit_arch": 0, + "v8_enable_extensible_ro_snapshot": 0, + "v8_enable_gdbjit": 0, + "v8_enable_hugepage": 0, + "v8_enable_i18n_support": 1, + "v8_enable_inspector": 1, + "v8_enable_javascript_promise_hooks": 1, + "v8_enable_lite_mode": 0, + "v8_enable_maglev": 0, + "v8_enable_object_print": 1, + "v8_enable_pointer_compression": 0, + "v8_enable_sandbox": 0, + "v8_enable_shared_ro_heap": 1, + "v8_enable_short_builtin_calls": 1, + "v8_enable_wasm_simd256_revec": 1, + "v8_enable_webassembly": 1, + "v8_optimized_debug": 1, + "v8_promise_internal_field_count": 1, + "v8_random_seed": 0, + "v8_trace_maps": 0, + "v8_use_siphash": 1, + "want_separate_host_toolset": 0, + "nodedir": "C:\\Users\\NAYIF\\AppData\\Local\\Temp\\prebuild\\node\\22.13.0", + "python": "C:\\Users\\NAYIF\\AppData\\Local\\Programs\\Python\\Python313\\python.exe", + "standalone_static_library": 1, + "msbuild_path": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe", + "target": "22.13.0", + "build_v8_with_gn": "false", + "cache": "C:\\Users\\NAYIF\\AppData\\Local\\npm-cache", + "globalconfig": "C:\\nvm4w\\nodejs\\etc\\npmrc", + "global_prefix": "C:\\nvm4w\\nodejs", + "init_module": "C:\\Users\\NAYIF\\.npm-init.js", + "local_prefix": "F:\\stremio-expo", + "node_gyp": "C:\\Users\\NAYIF\\AppData\\Local\\nvm\\v22.13.0\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js", + "npm_version": "11.0.0", + "prefix": "C:\\nvm4w\\nodejs", + "userconfig": "C:\\Users\\NAYIF\\.npmrc", + "user_agent": "npm/11.0.0 node/v22.13.0 win32 x64 workspaces/false" + } +} diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..8beb344 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,30 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace +.xcode.env.local + +# Bundle artifacts +*.jsbundle + +# CocoaPods +/Pods/ diff --git a/ios/.xcode.env b/ios/.xcode.env new file mode 100644 index 0000000..3d5782c --- /dev/null +++ b/ios/.xcode.env @@ -0,0 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use +export NODE_BINARY=$(command -v node) diff --git a/ios/Nuvio.xcodeproj/project.pbxproj b/ios/Nuvio.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7f56bb4 --- /dev/null +++ b/ios/Nuvio.xcodeproj/project.pbxproj @@ -0,0 +1,471 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; + 96905EF65AED1B983A6B3ABC /* libPods-Nuvio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Nuvio.a */; }; + B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; }; + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; + 53F59A3E794F4B87B221D4C3 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1EB3A22DCB47A0A12A0CAF /* noop-file.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 13B07F961A680F5B00A75B9A /* Nuvio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Nuvio.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Nuvio/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Nuvio/AppDelegate.mm; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Nuvio/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Nuvio/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Nuvio/main.m; sourceTree = ""; }; + 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Nuvio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Nuvio.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C2E3173556A471DD304B334 /* 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 = ""; }; + 7A4D352CD337FB3A3BF06240 /* 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 = ""; }; + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Nuvio/SplashScreen.storyboard; sourceTree = ""; }; + BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Nuvio/ExpoModulesProvider.swift"; sourceTree = ""; }; + DC1EB3A22DCB47A0A12A0CAF /* noop-file.swift */ = {isa = PBXFileReference; name = "noop-file.swift"; path = "Nuvio/noop-file.swift"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.swift; explicitFileType = undefined; includeInIndex = 0; }; + DD9A8A1855A34F10923D3C37 /* Nuvio-Bridging-Header.h */ = {isa = PBXFileReference; name = "Nuvio-Bridging-Header.h"; path = "Nuvio/Nuvio-Bridging-Header.h"; sourceTree = ""; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; explicitFileType = undefined; includeInIndex = 0; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 96905EF65AED1B983A6B3ABC /* libPods-Nuvio.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* Nuvio */ = { + isa = PBXGroup; + children = ( + BB2F792B24A3F905000567C9 /* Supporting */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.mm */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB71A68108700A75B9A /* main.m */, + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, + DC1EB3A22DCB47A0A12A0CAF /* noop-file.swift */, + DD9A8A1855A34F10923D3C37 /* Nuvio-Bridging-Header.h */, + ); + name = Nuvio; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Nuvio.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* Nuvio */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + D65327D7A22EEC0BE12398D9 /* Pods */, + D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* Nuvio.app */, + ); + name = Products; + sourceTree = ""; + }; + 92DBD88DE9BF7D494EA9DA96 /* Nuvio */ = { + isa = PBXGroup; + children = ( + FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */, + ); + name = Nuvio; + sourceTree = ""; + }; + BB2F792B24A3F905000567C9 /* Supporting */ = { + isa = PBXGroup; + children = ( + BB2F792C24A3F905000567C9 /* Expo.plist */, + ); + name = Supporting; + path = Nuvio/Supporting; + sourceTree = ""; + }; + D65327D7A22EEC0BE12398D9 /* Pods */ = { + isa = PBXGroup; + children = ( + 6C2E3173556A471DD304B334 /* Pods-Nuvio.debug.xcconfig */, + 7A4D352CD337FB3A3BF06240 /* Pods-Nuvio.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = { + isa = PBXGroup; + children = ( + 92DBD88DE9BF7D494EA9DA96 /* Nuvio */, + ); + name = ExpoModulesProviders; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* Nuvio */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Nuvio" */; + buildPhases = ( + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Nuvio; + productName = Nuvio; + productReference = 13B07F961A680F5B00A75B9A /* Nuvio.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1130; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1250; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Nuvio" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* Nuvio */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, + 8A293A5AD8A6486B86EDC6E7 /* Nuvio-Bridging-Header.h in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + 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`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; + }; + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Nuvio-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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; + }; + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */, + 53F59A3E794F4B87B221D4C3 /* noop-file.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-Nuvio.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "FB_SONARKIT_ENABLED=1", + ); + INFOPLIST_FILE = Nuvio/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app"; + PRODUCT_NAME = "Nuvio"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + TARGETED_DEVICE_FAMILY = "1,2"; + SWIFT_OBJC_BRIDGING_HEADER = Nuvio/Nuvio-Bridging-Header.h; + CODE_SIGN_ENTITLEMENTS = Nuvio/Nuvio.entitlements; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-Nuvio.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = Nuvio/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app"; + PRODUCT_NAME = "Nuvio"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + TARGETED_DEVICE_FAMILY = "1,2"; + SWIFT_OBJC_BRIDGING_HEADER = Nuvio/Nuvio-Bridging-Header.h; + CODE_SIGN_ENTITLEMENTS = Nuvio/Nuvio.entitlements; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Nuvio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Nuvio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/ios/Nuvio.xcodeproj/xcshareddata/xcschemes/Nuvio.xcscheme b/ios/Nuvio.xcodeproj/xcshareddata/xcschemes/Nuvio.xcscheme new file mode 100644 index 0000000..d56adf8 --- /dev/null +++ b/ios/Nuvio.xcodeproj/xcshareddata/xcschemes/Nuvio.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Nuvio/AppDelegate.h b/ios/Nuvio/AppDelegate.h new file mode 100644 index 0000000..1658a43 --- /dev/null +++ b/ios/Nuvio/AppDelegate.h @@ -0,0 +1,7 @@ +#import +#import +#import + +@interface AppDelegate : EXAppDelegateWrapper + +@end diff --git a/ios/Nuvio/AppDelegate.mm b/ios/Nuvio/AppDelegate.mm new file mode 100644 index 0000000..b27f832 --- /dev/null +++ b/ios/Nuvio/AppDelegate.mm @@ -0,0 +1,62 @@ +#import "AppDelegate.h" + +#import +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.moduleName = @"main"; + + // You can add your custom initial props in the dictionary below. + // They will be passed down to the ViewController used by React Native. + self.initialProps = @{}; + + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge +{ + return [self bundleURL]; +} + +- (NSURL *)bundleURL +{ +#if DEBUG + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; +#else + return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif +} + +// Linking API +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { + return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; +} + +// Universal Links +- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { + BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; + return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error +{ + return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + +@end diff --git a/ios/Nuvio/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/ios/Nuvio/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png new file mode 100644 index 0000000..d63e7ac Binary files /dev/null and b/ios/Nuvio/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png differ diff --git a/ios/Nuvio/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/Nuvio/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..90d8d4c --- /dev/null +++ b/ios/Nuvio/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images": [ + { + "filename": "App-Icon-1024x1024@1x.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/ios/Nuvio/Images.xcassets/Contents.json b/ios/Nuvio/Images.xcassets/Contents.json new file mode 100644 index 0000000..ed285c2 --- /dev/null +++ b/ios/Nuvio/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "expo" + } +} diff --git a/ios/Nuvio/Images.xcassets/SplashScreenBackground.colorset/Contents.json b/ios/Nuvio/Images.xcassets/SplashScreenBackground.colorset/Contents.json new file mode 100644 index 0000000..15f02ab --- /dev/null +++ b/ios/Nuvio/Images.xcassets/SplashScreenBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors": [ + { + "color": { + "components": { + "alpha": "1.000", + "blue": "1.00000000000000", + "green": "1.00000000000000", + "red": "1.00000000000000" + }, + "color-space": "srgb" + }, + "idiom": "universal" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/Contents.json b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/Contents.json new file mode 100644 index 0000000..f65c008 --- /dev/null +++ b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "image.png", + "scale": "1x" + }, + { + "idiom": "universal", + "filename": "image@2x.png", + "scale": "2x" + }, + { + "idiom": "universal", + "filename": "image@3x.png", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image.png b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image.png new file mode 100644 index 0000000..b9ff0fc Binary files /dev/null and b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image.png differ diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image@2x.png b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image@2x.png new file mode 100644 index 0000000..b9ff0fc Binary files /dev/null and b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image@2x.png differ diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image@3x.png b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image@3x.png new file mode 100644 index 0000000..b9ff0fc Binary files /dev/null and b/ios/Nuvio/Images.xcassets/SplashScreenLogo.imageset/image@3x.png differ diff --git a/ios/Nuvio/Info.plist b/ios/Nuvio/Info.plist new file mode 100644 index 0000000..30cbd48 --- /dev/null +++ b/ios/Nuvio/Info.plist @@ -0,0 +1,74 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Nuvio + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + com.nuvio.app + + + + CFBundleVersion + 1 + LSMinimumSystemVersion + 12.0 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UILaunchStoryboardName + SplashScreen + UIRequiredDeviceCapabilities + + arm64 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Light + UIViewControllerBasedStatusBarAppearance + + + \ No newline at end of file diff --git a/ios/Nuvio/Nuvio-Bridging-Header.h b/ios/Nuvio/Nuvio-Bridging-Header.h new file mode 100644 index 0000000..e11d920 --- /dev/null +++ b/ios/Nuvio/Nuvio-Bridging-Header.h @@ -0,0 +1,3 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// diff --git a/ios/Nuvio/Nuvio.entitlements b/ios/Nuvio/Nuvio.entitlements new file mode 100644 index 0000000..018a6e2 --- /dev/null +++ b/ios/Nuvio/Nuvio.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + \ No newline at end of file diff --git a/ios/Nuvio/SplashScreen.storyboard b/ios/Nuvio/SplashScreen.storyboard new file mode 100644 index 0000000..8a6fcd4 --- /dev/null +++ b/ios/Nuvio/SplashScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ios/Nuvio/Supporting/Expo.plist b/ios/Nuvio/Supporting/Expo.plist new file mode 100644 index 0000000..750be02 --- /dev/null +++ b/ios/Nuvio/Supporting/Expo.plist @@ -0,0 +1,12 @@ + + + + + EXUpdatesCheckOnLaunch + ALWAYS + EXUpdatesEnabled + + EXUpdatesLaunchWaitMs + 0 + + \ No newline at end of file diff --git a/ios/Nuvio/main.m b/ios/Nuvio/main.m new file mode 100644 index 0000000..25181b6 --- /dev/null +++ b/ios/Nuvio/main.m @@ -0,0 +1,10 @@ +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} + diff --git a/ios/Nuvio/noop-file.swift b/ios/Nuvio/noop-file.swift new file mode 100644 index 0000000..b2ffafb --- /dev/null +++ b/ios/Nuvio/noop-file.swift @@ -0,0 +1,4 @@ +// +// @generated +// A blank Swift file must be created for native modules with Swift files to work correctly. +// diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..a7242ef --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,66 @@ +require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") +require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") + +require 'json' +podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} + +ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0' +ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] + +platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1' +install! 'cocoapods', + :deterministic_uuids => false + +prepare_react_native_project! + +target 'Nuvio' do + use_expo_modules! + + if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' + config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]; + else + config_command = [ + 'node', + '--no-warnings', + '--eval', + 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))', + 'react-native-config', + '--json', + '--platform', + 'ios' + ] + end + + config = use_native_modules!(config_command) + + use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] + use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] + + use_react_native!( + :path => config[:reactNativePath], + :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/..", + :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', + ) + + post_install do |installer| + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true', + ) + + # This is necessary for Xcode 14, because it signs resource bundles by default + # when building for devices. + installer.target_installation_results.pod_target_installation_results + .each do |pod_name, target_installation_result| + target_installation_result.resource_bundle_targets.each do |resource_bundle_target| + resource_bundle_target.build_configurations.each do |config| + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + end + end + end + end +end diff --git a/ios/Podfile.properties.json b/ios/Podfile.properties.json new file mode 100644 index 0000000..417e2e5 --- /dev/null +++ b/ios/Podfile.properties.json @@ -0,0 +1,5 @@ +{ + "expo.jsEngine": "hermes", + "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true", + "newArchEnabled": "true" +} diff --git a/package-lock.json b/package-lock.json index 1103ae9..ab37d2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@expo/metro-runtime": "~4.0.1", "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "1.23.1", + "@react-native-community/blur": "^4.4.1", "@react-native-community/slider": "^4.5.6", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", @@ -22,6 +23,8 @@ "axios": "^1.8.4", "date-fns": "^4.1.0", "expo": "~52.0.43", + "expo-blur": "^14.0.3", + "expo-file-system": "^18.0.12", "expo-haptics": "~14.0.1", "expo-image": "~2.0.7", "expo-intent-launcher": "~12.0.2", @@ -35,13 +38,15 @@ "react-native": "0.76.9", "react-native-gesture-handler": "~2.20.2", "react-native-immersive-mode": "^2.0.2", + "react-native-modal": "^14.0.0-rc.1", "react-native-paper": "^5.13.1", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", "react-native-svg": "^15.8.0", "react-native-video": "^6.12.0", - "react-native-web": "~0.19.13" + "react-native-web": "~0.19.13", + "subsrt": "^1.1.1" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -3269,6 +3274,16 @@ "react-native": "^0.0.0-0 || >=0.60 <1.0" } }, + "node_modules/@react-native-community/blur": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.4.1.tgz", + "integrity": "sha512-XBSsRiYxE/MOEln2ayunShfJtWztHwUxLFcSL20o+HNNRnuUDv+GXkF6FmM2zE8ZUfrnhQ/zeTqvnuDPGw6O8A==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@react-native-community/slider": { "version": "4.5.6", "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.5.6.tgz", @@ -6572,6 +6587,17 @@ "react-native": "*" } }, + "node_modules/expo-blur": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-14.0.3.tgz", + "integrity": "sha512-BL3xnqBJbYm3Hg9t/HjNjdeY7N/q8eK5tsLYxswWG1yElISWZmMvrXYekl7XaVCPfyFyz8vQeaxd7q74ZY3Wrw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-constants": { "version": "17.0.8", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.0.8.tgz", @@ -10522,6 +10548,15 @@ } } }, + "node_modules/react-native-animatable": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.4.0.tgz", + "integrity": "sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.20.2", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz", @@ -10547,6 +10582,19 @@ "react-native": ">=0.60.5" } }, + "node_modules/react-native-modal": { + "version": "14.0.0-rc.1", + "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-14.0.0-rc.1.tgz", + "integrity": "sha512-v5pvGyx1FlmBzdHyPqBsYQyS2mIJhVmuXyNo5EarIzxicKhuoul6XasXMviGcXboEUT0dTYWs88/VendojPiVw==", + "license": "MIT", + "dependencies": { + "react-native-animatable": "1.4.0" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.70.0" + } + }, "node_modules/react-native-paper": { "version": "5.13.1", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.13.1.tgz", @@ -11885,6 +11933,15 @@ "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==", "license": "MIT" }, + "node_modules/subsrt": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/subsrt/-/subsrt-1.1.1.tgz", + "integrity": "sha512-F15pSRRrPYnLjDKohcwkUUGhgHH+0IqpCoznMHMibZI9a2qN3ya/p3Xp/7zFOPvwez9b/qFjwtOnyS7f7d/MNw==", + "license": "MIT", + "bin": { + "subsrt": "bin/subsrt.js" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/package.json b/package.json index 6d220d7..210ceea 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,10 @@ "web": "expo start --web" }, "dependencies": { + "@expo/metro-runtime": "~4.0.1", "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "1.23.1", + "@react-native-community/blur": "^4.4.1", "@react-native-community/slider": "^4.5.6", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", @@ -22,8 +24,11 @@ "axios": "^1.8.4", "date-fns": "^4.1.0", "expo": "~52.0.43", + "expo-blur": "^14.0.3", + "expo-file-system": "^18.0.12", "expo-haptics": "~14.0.1", "expo-image": "~2.0.7", + "expo-intent-launcher": "~12.0.2", "expo-linear-gradient": "~14.0.2", "expo-notifications": "~0.29.14", "expo-screen-orientation": "~8.0.4", @@ -34,15 +39,15 @@ "react-native": "0.76.9", "react-native-gesture-handler": "~2.20.2", "react-native-immersive-mode": "^2.0.2", + "react-native-modal": "^14.0.0-rc.1", "react-native-paper": "^5.13.1", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", "react-native-svg": "^15.8.0", "react-native-video": "^6.12.0", - "expo-intent-launcher": "~12.0.2", "react-native-web": "~0.19.13", - "@expo/metro-runtime": "~4.0.1" + "subsrt": "^1.1.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/src/components/NuvioHeader.tsx b/src/components/NuvioHeader.tsx index 44b8556..ce983e1 100644 --- a/src/components/NuvioHeader.tsx +++ b/src/components/NuvioHeader.tsx @@ -5,40 +5,49 @@ import { colors } from '../styles/colors'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/AppNavigator'; -import { LinearGradient } from 'expo-linear-gradient'; +import { BlurView as ExpoBlurView } from 'expo-blur'; +import { BlurView as CommunityBlurView } from '@react-native-community/blur'; type NavigationProp = NativeStackNavigationProp; export const NuvioHeader = () => { const navigation = useNavigation(); - + return ( - + + {Platform.OS === 'ios' ? ( + + ) : ( + + + + )} - NUVIO + + NUVIO + + navigation.navigate('Search')} > - + + + - + ); }; @@ -50,28 +59,71 @@ const styles = StyleSheet.create({ left: 0, right: 0, zIndex: 10, + overflow: 'hidden', }, - gradient: { - height: Platform.OS === 'ios' ? 100 : 90, - paddingTop: Platform.OS === 'ios' ? 40 : 24, + headerContainer: { + height: Platform.OS === 'ios' ? 85 : 75, + paddingTop: Platform.OS === 'ios' ? 35 : 20, + backgroundColor: 'rgba(0,0,0,0.3)', + }, + blurOverlay: { + ...StyleSheet.absoluteFillObject, + zIndex: -1, + }, + androidBlurContainer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + zIndex: -1, + overflow: 'hidden', + }, + androidBlur: { + flex: 1, + backgroundColor: 'transparent', }, contentContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 24, + justifyContent: 'space-between', + }, + titleContainer: { + position: 'relative', }, title: { fontSize: 32, fontWeight: '900', color: colors.white, - letterSpacing: 1, + letterSpacing: 2, fontFamily: Platform.OS === 'ios' ? 'System' : 'sans-serif-black', textTransform: 'uppercase', marginLeft: Platform.OS === 'ios' ? -4 : -8, + textShadowColor: 'rgba(255, 255, 255, 0.2)', + textShadowOffset: { width: 0, height: 1 }, + textShadowRadius: 4, + }, + titleAccent: { + position: 'absolute', + bottom: -3, + left: Platform.OS === 'ios' ? -2 : -6, + width: 24, + height: 2, + backgroundColor: colors.primary || '#3D85C6', + borderRadius: 1, }, searchButton: { - position: 'absolute', - right: 16, padding: 8, }, + iconWrapper: { + width: 40, + height: 40, + borderRadius: 20, + backgroundColor: 'rgba(255, 255, 255, 0.08)', + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.1)', + }, }); \ No newline at end of file diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 4a90b6e..508d876 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -557,6 +557,11 @@ const AppNavigator = () => { ({ marginTop: 0, marginBottom: 0, position: 'relative', + paddingTop: 56, }, featuredBanner: { width: '100%', diff --git a/src/screens/MetadataScreen.tsx b/src/screens/MetadataScreen.tsx index 43b035b..0b147cf 100644 --- a/src/screens/MetadataScreen.tsx +++ b/src/screens/MetadataScreen.tsx @@ -24,7 +24,10 @@ import { CastSection } from '../components/metadata/CastSection'; import { SeriesContent } from '../components/metadata/SeriesContent'; import { MovieContent } from '../components/metadata/MovieContent'; import { MoreLikeThisSection } from '../components/metadata/MoreLikeThisSection'; -import AgeBadge from '../components/metadata/AgeBadge'; +import { StreamingContent } from '../services/catalogService'; +import { GroupedStreams } from '../types/streams'; +import { TMDBEpisode } from '../services/tmdbService'; +import { Cast } from '../types/cast'; import { RouteParams, Episode } from '../types/metadata'; import Animated, { useAnimatedStyle, @@ -81,14 +84,10 @@ const MetadataScreen = () => { setMetadata, } = useMetadata({ id, type }); - const [showFullDescription, setShowFullDescription] = useState(false); const contentRef = useRef(null); const [lastScrollTop, setLastScrollTop] = useState(0); const [isFullDescriptionOpen, setIsFullDescriptionOpen] = useState(false); const fullDescriptionAnimation = useSharedValue(0); - const [textTruncated, setTextTruncated] = useState(false); - const descriptionHeight = useSharedValue(0); - const fullTextHeight = useSharedValue(0); // Animation values const screenScale = useSharedValue(0.8); @@ -112,7 +111,7 @@ const MetadataScreen = () => { const watchProgressOpacity = useSharedValue(0); // Debug log for route params - logger.log('[MetadataScreen] Component mounted with route params:', { id, type, episodeId }); + // logger.log('[MetadataScreen] Component mounted with route params:', { id, type, episodeId }); // Function to get episode details from episodeId const getEpisodeDetails = useCallback((episodeId: string) => { @@ -491,45 +490,6 @@ const MetadataScreen = () => { }); }; - const handleOpenFullDescription = useCallback(() => { - setIsFullDescriptionOpen(true); - fullDescriptionAnimation.value = withTiming(1, { - duration: 300, - easing: Easing.bezier(0.33, 0.01, 0, 1), - }); - }, []); - - const handleCloseFullDescription = useCallback(() => { - fullDescriptionAnimation.value = withTiming(0, { - duration: 250, - easing: Easing.bezier(0.33, 0.01, 0, 1), - }, () => { - runOnJS(setIsFullDescriptionOpen)(false); - }); - }, []); - - const fullDescriptionStyle = useAnimatedStyle(() => { - return { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: colors.darkBackground, - opacity: fullDescriptionAnimation.value, - transform: [ - { - translateY: interpolate( - fullDescriptionAnimation.value, - [0, 1], - [height, 0], - Extrapolate.CLAMP - ), - }, - ], - }; - }); - // Animated styles const containerAnimatedStyle = useAnimatedStyle(() => ({ flex: 1, @@ -603,7 +563,7 @@ const MetadataScreen = () => { if (tmdbId) { const credits = await tmdb.getCredits(tmdbId, type); - logger.log("Credits data structure:", JSON.stringify(credits).substring(0, 300)); + // logger.log("Credits data structure:", JSON.stringify(credits).substring(0, 300)); // Extract directors for movies if (type === 'movie' && credits.crew) { @@ -617,7 +577,7 @@ const MetadataScreen = () => { ...metadata, directors }); - logger.log("Updated directors:", directors); + // logger.log("Updated directors:", directors); } } @@ -638,7 +598,7 @@ const MetadataScreen = () => { ...metadata, creators: creators.slice(0, 3) // Limit to first 3 creators }); - logger.log("Updated creators:", creators.slice(0, 3)); + // logger.log("Updated creators:", creators.slice(0, 3)); } } } @@ -667,40 +627,6 @@ const MetadataScreen = () => { navigation.goBack(); }, [navigation]); - const descriptionAnimatedStyle = useAnimatedStyle(() => ({ - height: descriptionHeight.value, - opacity: interpolate( - descriptionHeight.value, - [0, fullTextHeight.value], - [0, 1], - Extrapolate.CLAMP - ) - })); - - // Function to handle text layout and store full height - const handleTextLayout = ({ nativeEvent: { lines } }: { nativeEvent: { lines: any[] } }) => { - if (!showFullDescription) { - setTextTruncated(lines.length > 3); - // Calculate height for 3 lines (24 is the lineHeight) - descriptionHeight.value = 3 * 24; - } - // Store full text height - fullTextHeight.value = lines.length * 24; - }; - - // Function to toggle description expansion with animation - const toggleDescription = () => { - setShowFullDescription(!showFullDescription); - descriptionHeight.value = withSpring( - !showFullDescription ? fullTextHeight.value : 3 * 24, - { - damping: 15, - stiffness: 100, - mass: 0.8 - } - ); - }; - if (loading) { return ( { {metadata.runtime} )} {metadata.certification && ( - + {metadata.certification} )} {metadata.imdbRating && ( @@ -900,29 +826,9 @@ const MetadataScreen = () => { {/* Description */} {metadata.description && ( - - + {`${metadata.description}`} - - {textTruncated && ( - - - {showFullDescription ? 'See less' : 'See more'} - - - - )} )} @@ -957,31 +863,6 @@ const MetadataScreen = () => { )} - - {/* Full Description Modal */} - {isFullDescriptionOpen && ( - - - - - - - About - - - - {metadata?.description} - - - - - )} ); diff --git a/src/screens/VideoPlayer.tsx b/src/screens/VideoPlayer.tsx index e6ea973..77a0f8d 100644 --- a/src/screens/VideoPlayer.tsx +++ b/src/screens/VideoPlayer.tsx @@ -14,12 +14,16 @@ import * as ScreenOrientation from 'expo-screen-orientation'; import { useRoute, useNavigation, RouteProp } from '@react-navigation/native'; import { RootStackParamList } from '../navigation/AppNavigator'; import { storageService } from '../services/storageService'; +// Import stremioService for subtitles +import { stremioService, Subtitle } from '../services/stremioService'; // Add throttle/debounce imports import { debounce } from 'lodash'; import { logger } from '../utils/logger'; +// Import FileSystem +import * as FileSystem from 'expo-file-system'; // Define the TrackPreferenceType for audio/text tracks -type TrackPreferenceType = 'system' | 'disabled' | 'title' | 'language' | 'index'; +type TrackPreferenceType = 'system' | 'disabled' | 'title' | 'language' | 'index' | 'uri'; // Define the SelectedTrack type for audio/text tracks interface SelectedTrack { @@ -87,20 +91,22 @@ const VideoPlayer = () => { const developmentTestUrl = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; const uri = __DEV__ && (!routeUri || routeUri.trim() === '') ? developmentTestUrl : routeUri; - // Log received props for debugging - logger.log("VideoPlayer received route params:", { - uri, - title, - season, - episode, - episodeTitle, - quality, - year, - streamProvider, - id, - type, - episodeId - }); + // Log received props for debugging (only once) + useEffect(() => { + logger.log("VideoPlayer received route params:", { + uri, + title, + season, + episode, + episodeTitle, + quality, + year, + streamProvider, + id, + type, + episodeId + }); + }, []); // Validate URI useEffect(() => { @@ -141,9 +147,6 @@ const VideoPlayer = () => { // Add timer ref for auto-hiding controls const hideControlsTimerRef = useRef(null); - // Add a new state variable to track buffering progress - const [bufferedProgress, setBufferedProgress] = useState(0); - // Add state for tracking if initial seek is done const [initialSeekDone, setInitialSeekDone] = useState(false); const lastProgressUpdate = useRef(0); @@ -154,6 +157,12 @@ const VideoPlayer = () => { // Add last onProgress update time to throttle updates const lastProgressUpdateTime = useRef(0); + // Add state for OpenSubtitles + const [subtitles, setSubtitles] = useState([]); + const [isLoadingSubtitles, setIsLoadingSubtitles] = useState(false); + // Add state to track the index of the active URI subtitle + const [selectedUriTrackIndex, setSelectedUriTrackIndex] = useState(null); + // Load initial progress when component mounts useEffect(() => { const loadProgress = async () => { @@ -230,6 +239,11 @@ const VideoPlayer = () => { const initialTimer = setTimeout(() => { startHideControlsTimer(); }, 1500); + + // Load subtitles if id and type are available + if (id && type) { + loadSubtitles(); + } // Disable immersive mode when component unmounts return () => { @@ -290,12 +304,6 @@ const VideoPlayer = () => { } }; - useEffect(() => { - if (duration > 0 && currentTime > 0) { - setSliderValue(currentTime); - } - }, [duration, currentTime]); - const formatTime = (seconds: number) => { if (isNaN(seconds)) return '0:00'; @@ -390,8 +398,8 @@ const VideoPlayer = () => { // Modify slider value change handler for community slider const onSliderValueChange = (value: number) => { + // Only update the slider value, don't update currentTime here setSliderValue(value); - setCurrentTime(value); }; const onSlidingComplete = (value: number) => { @@ -401,17 +409,10 @@ const VideoPlayer = () => { // Update UI immediately for responsive feel setCurrentTime(newTime); - setSliderValue(newTime); // Seek to the new position videoRef.current.seek(newTime); - // Reset buffered progress indicator if seeking forward - const isFastForward = newTime > currentTime; - if (isFastForward) { - setBufferedProgress(newTime); - } - // Reset timer for auto-hiding controls startHideControlsTimer(); @@ -459,12 +460,6 @@ const VideoPlayer = () => { setCurrentTime(newTime); setSliderValue(newTime); } - - // Update buffered progress more efficiently - if (data.playableDuration) { - // Use a functional update to ensure we're working with the latest state - setBufferedProgress(prev => Math.max(prev, data.playableDuration || 0)); - } }; const onLoad = (data: { duration: number }) => { @@ -480,7 +475,13 @@ const VideoPlayer = () => { const onTextTracks = (e: Readonly<{ textTracks: TextTrack[] }>) => { logger.log("Detected Text Tracks:", e.textTracks); - setTextTracks(e.textTracks || []); + // Only set built-in tracks, preserve OpenSubtitles tracks + const builtInTracks = e.textTracks || []; + setTextTracks(prevTracks => { + // Filter out existing built-in tracks (those with index < 100) + const openSubtitlesTracks = prevTracks.filter(t => t.index >= 100); + return [...builtInTracks, ...openSubtitlesTracks]; + }); }; // Toggle through aspect ratio modes @@ -626,8 +627,67 @@ const VideoPlayer = () => { }); }; - const selectSubtitleTrack = (track: SelectedTrack | null) => { - setSelectedTextTrack(track); + const selectSubtitleTrack = async (track: SelectedTrack | null) => { + // Reset previously selected external subtitle URI if any + // Also reset the tracked index + if (selectedTextTrack?.type === 'uri') { + setSelectedTextTrack(null); // Clear previous selection + setSelectedUriTrackIndex(null); + } + + // If selecting "Off" + if (!track || track.type === 'disabled') { + setSelectedTextTrack({ type: 'disabled' }); + setSelectedUriTrackIndex(null); + } + // If selecting a built-in track + else if (track.type === 'index' && typeof track.value === 'number' && track.value < 100) { + setSelectedTextTrack(track); + setSelectedUriTrackIndex(null); + } + // If selecting an OpenSubtitles track (index >= 100) + else if (track.type === 'index' && typeof track.value === 'number' && track.value >= 100) { + const subtitleIndex = track.value - 100; + const subtitle = subtitles[subtitleIndex]; + const originalTrackIndex = track.value; // Store the original index (>= 100) + + if (subtitle && subtitle.url) { + try { + logger.log(`Attempting to download subtitle from ${subtitle.url}`); + const localUri = `${FileSystem.cacheDirectory}${subtitle.id || Date.now()}.srt`; + + // Download the subtitle file + const downloadResult = await FileSystem.downloadAsync(subtitle.url, localUri); + + if (downloadResult.status === 200) { + logger.log(`Subtitle downloaded successfully to ${downloadResult.uri}`); + // Set the selected track to the local file URI + setSelectedTextTrack({ type: 'uri', value: downloadResult.uri }); + // Set the index of the active URI track + setSelectedUriTrackIndex(originalTrackIndex); + } else { + logger.error(`Failed to download subtitle: Status ${downloadResult.status}`); + setSelectedTextTrack({ type: 'disabled' }); // Fallback to disabled + setSelectedUriTrackIndex(null); + alert('Failed to download subtitle.'); + } + } catch (error) { + logger.error('Error downloading subtitle:', error); + setSelectedTextTrack({ type: 'disabled' }); // Fallback to disabled + setSelectedUriTrackIndex(null); + alert('Error downloading subtitle.'); + } + } else { + logger.warn('Selected OpenSubtitle track has no URL'); + setSelectedTextTrack({ type: 'disabled' }); + setSelectedUriTrackIndex(null); + } + } else { + // Handle any other cases or default + setSelectedTextTrack(track); + setSelectedUriTrackIndex(null); // Ensure reset for non-handled types + } + // Hide the subtitle menu with animation Animated.timing(subtitleSlideAnim, { toValue: 400, @@ -638,6 +698,40 @@ const VideoPlayer = () => { }); }; + // Function to load subtitles from OpenSubtitles v3 addon + const loadSubtitles = async () => { + if (!id || !type) return; + + try { + setIsLoadingSubtitles(true); + logger.log(`Loading subtitles for ${type} with id ${id}${episodeId ? ` and episodeId ${episodeId}` : ''}`); + + const subtitlesList = await stremioService.getSubtitles(type, id, episodeId); + + if (subtitlesList && subtitlesList.length > 0) { + logger.log(`Loaded ${subtitlesList.length} subtitles`); + setSubtitles(subtitlesList); + + // Convert OpenSubtitles subtitles to the format expected by react-native-video + const convertedTracks: TextTrack[] = subtitlesList.map((sub, index) => ({ + index: 100 + index, // Use high index values to avoid conflicts + title: sub.id, + language: sub.lang, + type: 'text' + })); + + // Combine with existing text tracks + setTextTracks(prevTracks => [...prevTracks, ...convertedTracks]); + } else { + logger.log('No subtitles found'); + } + } catch (error) { + logger.error('Error loading subtitles:', error); + } finally { + setIsLoadingSubtitles(false); + } + }; + return ( { {/* Slider - replaced with community slider */} - {/* Buffer indicator - only render if needed */} - {bufferedProgress > 0 && ( - - )} - {formatTime(currentTime)} @@ -831,7 +914,9 @@ const VideoPlayer = () => { {selectedTextTrack?.type === 'disabled' ? 'Subtitles (Off)' - : `Subtitles (${textTracks.find(t => t.index === selectedTextTrack?.value)?.language?.toUpperCase() || 'On'})`} + : `Subtitles (${(selectedTextTrack?.type === 'uri' && selectedUriTrackIndex !== null) + ? textTracks.find(t => t.index === selectedUriTrackIndex)?.language?.toUpperCase() || 'On' + : textTracks.find(t => t.index === selectedTextTrack?.value)?.language?.toUpperCase() || 'On'})`} @@ -928,21 +1013,38 @@ const VideoPlayer = () => { key={track.index} style={[ styles.optionItem, - selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index && styles.selectedOption + // Updated highlighting logic: + (selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index) || + (selectedUriTrackIndex === track.index) // Highlight if this URI track is active + ? styles.selectedOption + : null ]} onPress={() => selectSubtitleTrack({ type: 'index', value: track.index })} > {track.language ? track.language.toUpperCase() : (track.title || `Track ${track.index + 1}`)} + {track.index >= 100 && ' (OpenSubtitles)'} ))} @@ -1071,16 +1173,6 @@ const styles = StyleSheet.create({ alignItems: 'center', width: '100%', }, - bufferIndicator: { - position: 'absolute', - height: 4, - backgroundColor: 'rgba(255, 255, 255, 0.25)', - top: 14, // Position to align with the slider track - left: 24, // Account for the currentTime text - right: 24, // Account for the duration text - zIndex: 1, - borderRadius: 2, - }, slider: { flex: 1, height: 40, diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts index 6b3cb08..81e8c94 100644 --- a/src/services/cacheService.ts +++ b/src/services/cacheService.ts @@ -17,6 +17,8 @@ class CacheService { private cache: Map = new Map(); private metadataScreenCache: Map = new Map(); private readonly MAX_METADATA_SCREENS = 5; + private readonly MAX_CACHE_SIZE = 100; // Max size for the main cache + private readonly CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours TTL for main cache items private constructor() { // Initialize any other necessary properties @@ -33,128 +35,227 @@ class CacheService { return `${type}:${id}`; } + // Helper to ensure the main cache does not exceed its size limit + private ensureCacheLimit(): void { + if (this.cache.size >= this.MAX_CACHE_SIZE) { + // Remove the least recently used item (first key in Map iteration order) + const oldestKey = this.cache.keys().next().value; + if (oldestKey) { + this.cache.delete(oldestKey); + } + } + } + + // Helper to mark an item as recently used (by deleting and re-inserting) + // Also ensures the timestamp is updated. + private touch(key: string, existingData: CachedContent): void { + this.cache.delete(key); + this.cache.set(key, { ...existingData, timestamp: Date.now() }); + } + public setMetadata(id: string, type: string, metadata: StreamingContent): void { const key = this.getCacheKey(id, type); - const existing = this.cache.get(key); - this.cache.set(key, { - ...(existing || {}), - metadata, - timestamp: Date.now() - } as CachedContent); + let existing = this.cache.get(key); + + if (existing) { + // Update existing entry and mark as recent + existing = { ...existing, metadata, timestamp: Date.now() }; + this.touch(key, existing); + } else { + // Adding a new entry, first check limit + this.ensureCacheLimit(); + // Add the new entry + this.cache.set(key, { + metadata, + timestamp: Date.now() + } as CachedContent); + } } public setStreams(id: string, type: string, streams: GroupedStreams): void { const key = this.getCacheKey(id, type); const existing = this.cache.get(key); - if (!existing?.metadata) return; - - this.cache.set(key, { + // Can only set streams if metadata already exists in cache + if (!existing?.metadata) return; + + const updatedData = { ...existing, streams, - timestamp: Date.now() - }); + timestamp: Date.now() // Update timestamp on modification + }; + this.touch(key, updatedData); // Mark as recently used } public setEpisodes(id: string, type: string, episodes: TMDBEpisode[]): void { const key = this.getCacheKey(id, type); const existing = this.cache.get(key); if (!existing?.metadata) return; - - this.cache.set(key, { + + const updatedData = { ...existing, episodes, timestamp: Date.now() - }); + }; + this.touch(key, updatedData); } public setCast(id: string, type: string, cast: Cast[]): void { const key = this.getCacheKey(id, type); const existing = this.cache.get(key); if (!existing?.metadata) return; - - this.cache.set(key, { + + const updatedData = { ...existing, cast, timestamp: Date.now() - }); + }; + this.touch(key, updatedData); } public setEpisodeStreams(id: string, type: string, episodeId: string, streams: GroupedStreams): void { const key = this.getCacheKey(id, type); const existing = this.cache.get(key); if (!existing?.metadata) return; - - this.cache.set(key, { + + const updatedData = { ...existing, episodeStreams: { ...(existing.episodeStreams || {}), [episodeId]: streams }, timestamp: Date.now() - }); + }; + this.touch(key, updatedData); } + // --- Getters for the main cache --- + public getMetadata(id: string, type: string): StreamingContent | null { const key = this.getCacheKey(id, type); - return this.cache.get(key)?.metadata || null; + const data = this.cache.get(key); + if (data) { + // Check for expiration first + if (Date.now() - data.timestamp > this.CACHE_TTL_MS) { + this.cache.delete(key); // Remove expired item + return null; + } + // Not expired, proceed with LRU update + this.touch(key, data); // Mark as recently used on access + return data.metadata; + } + return null; } public getStreams(id: string, type: string): GroupedStreams | null { const key = this.getCacheKey(id, type); - return this.cache.get(key)?.streams || null; + const data = this.cache.get(key); + if (data) { + // Check for expiration first + if (Date.now() - data.timestamp > this.CACHE_TTL_MS) { + this.cache.delete(key); // Remove expired item + return null; + } + // Not expired, proceed with LRU update + this.touch(key, data); // Mark as recently used on access + return data.streams || null; + } + return null; } public getEpisodes(id: string, type: string): TMDBEpisode[] | null { const key = this.getCacheKey(id, type); - return this.cache.get(key)?.episodes || null; + const data = this.cache.get(key); + if (data) { + // Check for expiration first + if (Date.now() - data.timestamp > this.CACHE_TTL_MS) { + this.cache.delete(key); // Remove expired item + return null; + } + // Not expired, proceed with LRU update + this.touch(key, data); // Mark as recently used on access + return data.episodes || null; + } + return null; } public getCast(id: string, type: string): Cast[] | null { const key = this.getCacheKey(id, type); - return this.cache.get(key)?.cast || null; + const data = this.cache.get(key); + if (data) { + // Check for expiration first + if (Date.now() - data.timestamp > this.CACHE_TTL_MS) { + this.cache.delete(key); // Remove expired item + return null; + } + // Not expired, proceed with LRU update + this.touch(key, data); // Mark as recently used on access + return data.cast || null; + } + return null; } public getEpisodeStreams(id: string, type: string, episodeId: string): GroupedStreams | null { const key = this.getCacheKey(id, type); - return this.cache.get(key)?.episodeStreams?.[episodeId] || null; + const data = this.cache.get(key); + if (data) { + // Check for expiration first + if (Date.now() - data.timestamp > this.CACHE_TTL_MS) { + this.cache.delete(key); // Remove expired item + return null; + } + // Not expired, check if episode stream exists and proceed with LRU update + if (data.episodeStreams?.[episodeId]) { + this.touch(key, data); // Mark as recently used on access + return data.episodeStreams[episodeId]; + } + } + return null; } + // --- Cache utility methods --- + public clearCache(): void { this.cache.clear(); } + // Checks existence without affecting LRU order public isCached(id: string, type: string): boolean { const key = this.getCacheKey(id, type); return this.cache.has(key); } + // --- Metadata Screen Cache (Separate LRU logic) --- + public cacheMetadataScreen(id: string, type: string, data: any) { if (!id || !type) return; - + const key = `${type}:${id}`; - - // If this item is already in cache, just update it + + // If this item is already in cache, delete to re-insert at the end (most recent) if (this.metadataScreenCache.has(key)) { this.metadataScreenCache.delete(key); - this.metadataScreenCache.set(key, data); - return; } - - // If we've reached the limit, remove the oldest item - if (this.metadataScreenCache.size >= this.MAX_METADATA_SCREENS) { + // If we've reached the limit, remove the oldest item (first key) + else if (this.metadataScreenCache.size >= this.MAX_METADATA_SCREENS) { const firstKey = this.metadataScreenCache.keys().next().value; if (firstKey) { this.metadataScreenCache.delete(firstKey); } } - // Add the new item + // Add the new/updated item (makes it the most recent) this.metadataScreenCache.set(key, data); } public getMetadataScreen(id: string, type: string) { const key = `${type}:${id}`; - return this.metadataScreenCache.get(key); + const data = this.metadataScreenCache.get(key); + // If found, mark as recently used by re-inserting + if (data) { + this.metadataScreenCache.delete(key); + this.metadataScreenCache.set(key, data); + } + return data; } public clearMetadataScreenCache() { diff --git a/src/services/stremioService.ts b/src/services/stremioService.ts index 6a329c6..1be1d50 100644 --- a/src/services/stremioService.ts +++ b/src/services/stremioService.ts @@ -22,6 +22,15 @@ export interface Meta { certification?: string; } +export interface Subtitle { + id: string; + url: string; + lang: string; + fps?: number; + addon?: string; + addonName?: string; +} + export interface Stream { name?: string; title?: string; @@ -48,6 +57,12 @@ export interface StreamResponse { addonName: string; } +export interface SubtitleResponse { + subtitles: Subtitle[]; + addon: string; + addonName: string; +} + // Modify the callback signature to include addon ID interface StreamCallback { (streams: Stream[] | null, addonId: string | null, addonName: string | null, error: Error | null): void; @@ -124,8 +139,8 @@ class StremioService { private installedAddons: Map = new Map(); private readonly STORAGE_KEY = 'stremio-addons'; private readonly DEFAULT_ADDONS = [ - 'https://v3-cinemeta.strem.io/manifest.json' - + 'https://v3-cinemeta.strem.io/manifest.json', + 'https://opensubtitles-v3.strem.io/manifest.json' ]; private readonly MAX_CONCURRENT_REQUESTS = 3; private readonly DEFAULT_PAGE_SIZE = 50; @@ -720,6 +735,54 @@ class StremioService { items: items.slice(0, limit) }; } + + async getSubtitles(type: string, id: string, videoId?: string): Promise { + await this.ensureInitialized(); + + // Find the OpenSubtitles v3 addon + const openSubtitlesAddon = this.getInstalledAddons().find( + addon => addon.id === 'org.stremio.opensubtitlesv3' + ); + + if (!openSubtitlesAddon) { + logger.warn('OpenSubtitles v3 addon not found'); + return []; + } + + try { + const baseUrl = this.getAddonBaseURL(openSubtitlesAddon.url || ''); + + // Construct the query URL with the correct format + // For series episodes, use the videoId directly which includes series ID + episode info + let url = ''; + if (type === 'series' && videoId) { + // For series, the format should be /subtitles/series/tt12345:1:2.json + url = `${baseUrl}/subtitles/${type}/${videoId}.json`; + } else { + // For movies, the format is /subtitles/movie/tt12345.json + url = `${baseUrl}/subtitles/${type}/${id}.json`; + } + + logger.log(`Fetching subtitles from: ${url}`); + + const response = await this.retryRequest(async () => { + return await axios.get(url, { timeout: 10000 }); + }); + + if (response.data && response.data.subtitles) { + // Process and return the subtitles + return response.data.subtitles.map((sub: any) => ({ + ...sub, + addon: openSubtitlesAddon.id, + addonName: openSubtitlesAddon.name + })); + } + } catch (error) { + logger.error('Failed to fetch subtitles:', error); + } + + return []; + } } export const stremioService = StremioService.getInstance();