Made metadascreen as modal for better user experience in ios

This commit is contained in:
Nayif Noushad 2025-04-14 18:56:42 +05:30
parent 975f0eb9ef
commit 8de78b435a
34 changed files with 1947 additions and 265 deletions

View file

@ -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": {

466
build/config.gypi Normal file
View file

@ -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"
}
}

30
ios/.gitignore vendored Normal file
View file

@ -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/

11
ios/.xcode.env Normal file
View file

@ -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)

View file

@ -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 = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Nuvio/AppDelegate.mm; sourceTree = "<group>"; };
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>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Nuvio/main.m; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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; };
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Nuvio/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
DC1EB3A22DCB47A0A12A0CAF /* noop-file.swift */ = {isa = PBXFileReference; name = "noop-file.swift"; path = "Nuvio/noop-file.swift"; sourceTree = "<group>"; 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 = "<group>"; 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 = "<group>";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Nuvio.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* Nuvio */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
D65327D7A22EEC0BE12398D9 /* Pods */,
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* Nuvio.app */,
);
name = Products;
sourceTree = "<group>";
};
92DBD88DE9BF7D494EA9DA96 /* Nuvio */ = {
isa = PBXGroup;
children = (
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */,
);
name = Nuvio;
sourceTree = "<group>";
};
BB2F792B24A3F905000567C9 /* Supporting */ = {
isa = PBXGroup;
children = (
BB2F792C24A3F905000567C9 /* Expo.plist */,
);
name = Supporting;
path = Nuvio/Supporting;
sourceTree = "<group>";
};
D65327D7A22EEC0BE12398D9 /* Pods */ = {
isa = PBXGroup;
children = (
6C2E3173556A471DD304B334 /* Pods-Nuvio.debug.xcconfig */,
7A4D352CD337FB3A3BF06240 /* Pods-Nuvio.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = {
isa = PBXGroup;
children = (
92DBD88DE9BF7D494EA9DA96 /* Nuvio */,
);
name = ExpoModulesProviders;
sourceTree = "<group>";
};
/* 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 */;
}

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "Nuvio.app"
BlueprintName = "Nuvio"
ReferencedContainer = "container:Nuvio.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "NuvioTests.xctest"
BlueprintName = "NuvioTests"
ReferencedContainer = "container:Nuvio.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "Nuvio.app"
BlueprintName = "Nuvio"
ReferencedContainer = "container:Nuvio.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "Nuvio.app"
BlueprintName = "Nuvio"
ReferencedContainer = "container:Nuvio.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

7
ios/Nuvio/AppDelegate.h Normal file
View file

@ -0,0 +1,7 @@
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
#import <Expo/Expo.h>
@interface AppDelegate : EXAppDelegateWrapper
@end

62
ios/Nuvio/AppDelegate.mm Normal file
View file

@ -0,0 +1,62 @@
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
@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<UIApplicationOpenURLOptionsKey,id> *)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<id<UIUserActivityRestoring>> * _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

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

View file

@ -0,0 +1,14 @@
{
"images": [
{
"filename": "App-Icon-1024x1024@1x.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"version": 1,
"author": "expo"
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "expo"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

74
ios/Nuvio/Info.plist Normal file
View file

@ -0,0 +1,74 @@
<?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.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.nuvio.app</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<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>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,3 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View file

@ -0,0 +1,8 @@
<?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>
</dict>
</plist>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" 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"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<scene sceneID="EXPO-SCENE-1">
<objects>
<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" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView id="EXPO-SplashScreen" userLabel="SplashScreenLogo" image="SplashScreenLogo" contentMode="scaleAspectFit" clipsSubviews="true" userInteractionEnabled="false" translatesAutoresizingMaskIntoConstraints="false">
<rect key="frame" x="0" y="0" width="414" height="736"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
<constraints>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="83fcb9b545b870ba44c24f0feeb116490c499c52"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="61d16215e44b98e39d0a2c74fdbfaaa22601b12c"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="f934da460e9ab5acae3ad9987d5b676a108796c1"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="d6a0be88096b36fb132659aa90203d39139deda9"/>
</constraints>
<color key="backgroundColor" name="SplashScreenBackground"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="0.0" y="0.0"/>
</scene>
</scenes>
<resources>
<image name="SplashScreenLogo" width="414" height="736"/>
<namedColor name="SplashScreenBackground">
<color alpha="1.000" blue="1.00000000000000" green="1.00000000000000" red="1.00000000000000" customColorSpace="sRGB" colorSpace="custom"/>
</namedColor>
</resources>
</document>

View file

@ -0,0 +1,12 @@
<?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>EXUpdatesCheckOnLaunch</key>
<string>ALWAYS</string>
<key>EXUpdatesEnabled</key>
<false/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
</dict>
</plist>

10
ios/Nuvio/main.m Normal file
View file

@ -0,0 +1,10 @@
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View file

@ -0,0 +1,4 @@
//
// @generated
// A blank Swift file must be created for native modules with Swift files to work correctly.
//

66
ios/Podfile Normal file
View file

@ -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

View file

@ -0,0 +1,5 @@
{
"expo.jsEngine": "hermes",
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
"newArchEnabled": "true"
}

59
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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<RootStackParamList>;
export const NuvioHeader = () => {
const navigation = useNavigation<NavigationProp>();
return (
<View style={styles.container}>
<LinearGradient
colors={[
'#000000',
'rgba(0, 0, 0, 0.95)',
'rgba(0, 0, 0, 0.8)',
'rgba(0, 0, 0, 0.2)',
'transparent'
]}
locations={[0, 0.3, 0.6, 0.8, 1]}
style={styles.gradient}
>
<View style={styles.headerContainer}>
{Platform.OS === 'ios' ? (
<ExpoBlurView intensity={60} style={styles.blurOverlay} tint="dark" />
) : (
<View style={styles.androidBlurContainer}>
<CommunityBlurView
style={styles.androidBlur}
blurType="dark"
blurAmount={8}
overlayColor="rgba(0,0,0,0.4)"
reducedTransparencyFallbackColor="black"
/>
</View>
)}
<View style={styles.contentContainer}>
<Text style={styles.title}>NUVIO</Text>
<View style={styles.titleContainer}>
<Text style={styles.title}>NUVIO</Text>
<View style={styles.titleAccent} />
</View>
<TouchableOpacity
style={styles.searchButton}
onPress={() => navigation.navigate('Search')}
>
<MaterialCommunityIcons
name="magnify"
size={28}
color={colors.white}
/>
<View style={styles.iconWrapper}>
<MaterialCommunityIcons
name="magnify"
size={24}
color={colors.white}
/>
</View>
</TouchableOpacity>
</View>
</LinearGradient>
</View>
</View>
);
};
@ -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)',
},
});

View file

@ -557,6 +557,11 @@ const AppNavigator = () => {
<Stack.Screen
name="Streams"
component={StreamsScreen as any}
options={{
headerShown: false,
animation: Platform.OS === 'ios' ? 'slide_from_bottom' : 'fade_from_bottom',
...(Platform.OS === 'ios' && { presentation: 'modal' }),
}}
/>
<Stack.Screen
name="Player"

View file

@ -904,6 +904,7 @@ const styles = StyleSheet.create<any>({
marginTop: 0,
marginBottom: 0,
position: 'relative',
paddingTop: 56,
},
featuredBanner: {
width: '100%',

View file

@ -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<ScrollView>(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 (
<SafeAreaView
@ -861,7 +787,7 @@ const MetadataScreen = () => {
<Text style={styles.metaText}>{metadata.runtime}</Text>
)}
{metadata.certification && (
<AgeBadge rating={metadata.certification} />
<Text style={styles.metaText}>{metadata.certification}</Text>
)}
{metadata.imdbRating && (
<View style={styles.ratingContainer}>
@ -900,29 +826,9 @@ const MetadataScreen = () => {
{/* Description */}
{metadata.description && (
<View style={styles.descriptionContainer}>
<Animated.View style={descriptionAnimatedStyle}>
<Text
style={styles.description}
onTextLayout={handleTextLayout}
>
<Text style={styles.description}>
{`${metadata.description}`}
</Text>
</Animated.View>
{textTruncated && (
<TouchableOpacity
onPress={toggleDescription}
style={styles.showMoreButton}
>
<Text style={styles.showMoreText}>
{showFullDescription ? 'See less' : 'See more'}
</Text>
<MaterialIcons
name={showFullDescription ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
size={20}
color={colors.textMuted}
/>
</TouchableOpacity>
)}
</View>
)}
@ -957,31 +863,6 @@ const MetadataScreen = () => {
)}
</Animated.View>
</ScrollView>
{/* Full Description Modal */}
{isFullDescriptionOpen && (
<Animated.View style={fullDescriptionStyle}>
<SafeAreaView style={styles.fullDescriptionContainer}>
<View style={styles.fullDescriptionHeader}>
<TouchableOpacity
onPress={handleCloseFullDescription}
style={styles.fullDescriptionCloseButton}
>
<MaterialIcons name="close" size={24} color={colors.text} />
</TouchableOpacity>
<Text style={styles.fullDescriptionTitle}>About</Text>
</View>
<ScrollView
style={styles.fullDescriptionContent}
showsVerticalScrollIndicator={false}
>
<Text style={styles.fullDescriptionText}>
{metadata?.description}
</Text>
</ScrollView>
</SafeAreaView>
</Animated.View>
)}
</Animated.View>
</SafeAreaView>
);

View file

@ -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<NodeJS.Timeout | null>(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<number>(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<Subtitle[]>([]);
const [isLoadingSubtitles, setIsLoadingSubtitles] = useState(false);
// Add state to track the index of the active URI subtitle
const [selectedUriTrackIndex, setSelectedUriTrackIndex] = useState<number | null>(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 (
<View style={styles.container}>
<TouchableOpacity
@ -755,17 +849,6 @@ const VideoPlayer = () => {
<View style={styles.bottomControls}>
{/* Slider - replaced with community slider */}
<View style={styles.sliderContainer}>
{/* Buffer indicator - only render if needed */}
{bufferedProgress > 0 && (
<View style={[
styles.bufferIndicator,
{
width: `${Math.min((bufferedProgress / (duration || 1)) * 100, 100)}%`
}
]}
/>
)}
<View style={styles.sliderRow}>
<Text style={styles.currentTime}>
{formatTime(currentTime)}
@ -831,7 +914,9 @@ const VideoPlayer = () => {
<Text style={[styles.bottomButtonText, textTracks.length === 0 && {color: 'grey'}]}>
{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'})`}
</Text>
</TouchableOpacity>
</View>
@ -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 })}
>
<Ionicons
name={selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index ? "radio-button-on" : "radio-button-off"}
// Updated icon logic:
name={((selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index) ||
(selectedUriTrackIndex === track.index))
? "radio-button-on"
: "radio-button-off"}
size={18}
color={selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index ? "#E50914" : "white"}
// Updated color logic:
color={((selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index) ||
(selectedUriTrackIndex === track.index))
? "#E50914"
: "white"}
style={{ marginRight: 10 }}
/>
<Text style={[
styles.optionItemText,
selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index && styles.selectedOptionText
// Updated text style logic:
((selectedTextTrack?.type === 'index' && selectedTextTrack?.value === track.index) ||
(selectedUriTrackIndex === track.index))
? styles.selectedOptionText
: null
]}>
{track.language ? track.language.toUpperCase() : (track.title || `Track ${track.index + 1}`)}
{track.index >= 100 && ' (OpenSubtitles)'}
</Text>
</TouchableOpacity>
))}
@ -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,

View file

@ -17,6 +17,8 @@ class CacheService {
private cache: Map<string, CachedContent> = new Map();
private metadataScreenCache: Map<string, any> = 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() {

View file

@ -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<string, Manifest> = 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<Subtitle[]> {
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();