Merge pull request #23 from jjbrunton/staging
Release v1.0.8: Expo SDK 56 and EAS Observe
No files matched your search
@@ -3,8 +3,8 @@ id: 2026-08-30-ios-extension-version-verification
|
||||
date: 2026-08-30
|
||||
status: promoted
|
||||
scope: iOS release artifact verification
|
||||
evidence: build 45 Xcode archive warning plus direct PlistBuddy inspection of the exported IPA
|
||||
destination: .codex/skills/release-mobile/SKILL.md
|
||||
evidence: v1.0.6 exported IPA inspection and reproduced v1.0.7 Xcode build-setting mismatch
|
||||
destination: apps/mobile/scripts/release-preflight.mjs and .codex/skills/release-mobile/SKILL.md
|
||||
review_after: 2027-02-28
|
||||
---
|
||||
|
||||
@@ -21,19 +21,23 @@ and every packaged extension `Info.plist` with `PlistBuddy`.
|
||||
|
||||
## Cause
|
||||
|
||||
The archive warning described an intermediate state and was not authoritative
|
||||
for the exported package. Direct IPA inspection showed both the app and
|
||||
`LiveDrawWidget.appex` at version 1.0.6 build 45.
|
||||
The v1.0.6 archive warning described an intermediate state and was not
|
||||
authoritative for that exported package. The later v1.0.7 reproduction found a
|
||||
separate real hazard: `CURRENT_PROJECT_VERSION` in both LiveDrawWidget
|
||||
configurations can override the widget plist and package an older build number.
|
||||
|
||||
## Remediation
|
||||
|
||||
For every iOS release, inspect version and build values in the exported app and
|
||||
all packaged extensions. Reject or rebuild only when the final IPA values differ;
|
||||
do not accept or reject a release from archive warnings alone.
|
||||
Before every iOS release, require public-version parity across app config, both
|
||||
plists, and Xcode marketing settings. Require the widget Debug and Release
|
||||
`CURRENT_PROJECT_VERSION` values to equal both plists. Then inspect the exported
|
||||
app and all packaged extensions. Reject or rebuild only when the final IPA values
|
||||
differ; do not accept or reject a release from archive warnings alone.
|
||||
|
||||
## Evidence
|
||||
|
||||
`PlistBuddy` read `CFBundleShortVersionString` 1.0.6 and `CFBundleVersion` 45 from
|
||||
both packaged plists. `codesign --verify --deep --strict` passed for the app, and
|
||||
the verified IPA SHA-256 was
|
||||
`9d4779bddac4647911a3a38e4ee37cf794c2b8691e0746a041b629c62b308e7f`.
|
||||
both v1.0.6 packaged plists. The v1.0.7 build later proved a stale widget Xcode
|
||||
setting can survive plist-only review. `release-preflight.mjs` and its node test
|
||||
now reject that mismatch before EAS allocation; exported IPA inspection remains
|
||||
the final authority.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
id: 2026-08-31-mobile-release-preflight-and-attempts
|
||||
date: 2026-08-31
|
||||
status: promoted
|
||||
scope: mobile production release preparation and local EAS builds
|
||||
evidence: v1.0.7 Android and iOS local EAS release investigation and verified artifacts
|
||||
destination: apps/mobile/scripts/release-preflight.mjs, apps/mobile/scripts/release-preflight.test.mjs, docs/releasing.md, and release skills
|
||||
review_after: 2027-02-28
|
||||
---
|
||||
|
||||
## Symptom
|
||||
|
||||
The v1.0.7 release spent repeated attempts on archive, plugin startup, package
|
||||
installation, disk-space, signing, and iOS extension-version failures before
|
||||
valid Android and iOS artifacts were accepted.
|
||||
|
||||
## Reproduction
|
||||
|
||||
Run a local production EAS build with an ignored public OTA certificate, a
|
||||
package-directory plugin path, insufficient disk space, or divergent widget
|
||||
`CURRENT_PROJECT_VERSION`. Each condition fails or produces an invalid input
|
||||
before a valid final artifact can be accepted.
|
||||
|
||||
## Cause
|
||||
|
||||
Pre-compilation setup failures were treated as rebuild failures. Source parity,
|
||||
archive inclusion, local EAS plugin shape, disk capacity, and effective iOS widget
|
||||
build settings were not checked together before EAS allocation.
|
||||
|
||||
## Remediation
|
||||
|
||||
Run the deterministic mobile release preflight before every production build.
|
||||
Classify attempts before retrying: archive, plugin, npm, and ENOSPC failures do
|
||||
not consume a native build; signing, profile, and capability failures require
|
||||
provider read-back before one replacement build. Retain work directories and logs
|
||||
at stable ignored paths. Accept only independently inspected artifacts and keep
|
||||
rejected artifacts out of the upload path.
|
||||
|
||||
The Android native input failure was a Gradle task ordering issue: Prefab inputs
|
||||
must be available before dependent native tasks. Preserve the explicit ordering
|
||||
fix rather than removing Firebase or changing dependency versions without proof.
|
||||
|
||||
## Evidence
|
||||
|
||||
The preflight node tests reject an ignored OTA certificate and an effective widget
|
||||
build-setting mismatch. The checked-in source invariants pass the preflight. The
|
||||
maintained release guide and skills record local EAS path requirements, provider
|
||||
read-back, phase-specific status language, artifact inspection, store read-back,
|
||||
and scoped upload authorization boundaries.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: release-mobile
|
||||
description: Prepare and verify Sauci iOS or Android releases and store uploads.
|
||||
description: Prepare, verify, and ship Sauci iOS or Android releases end to end, including store uploads, review submission, and rollout.
|
||||
---
|
||||
|
||||
Read `.codex/skills/release-mobile/SKILL.md` completely and follow it.
|
||||
@@ -13,9 +13,13 @@ drafting notes and [references/artifact-verification.md](references/artifact-ver
|
||||
before accepting a build.
|
||||
|
||||
A request to generate or create a release authorizes the scoped version edits,
|
||||
release notes, local builds, release commits, and one local annotated tag. It
|
||||
does not authorize pushing commits or tags, uploading binaries, submitting a
|
||||
store release, changing signing credentials, or regenerating native projects.
|
||||
release notes, local builds, release commits, and one local annotated tag. When
|
||||
the request also asks to ship, upload, submit, or release to the stores, it
|
||||
authorizes the complete store pipeline for the stated platforms after the exact
|
||||
artifact has been verified: upload, App Review / Google Play review submission,
|
||||
and rollout, including full production rollout (see the `release-mobile` skill).
|
||||
It does not authorize pushing commits or tags, changing signing credentials, or
|
||||
regenerating native projects unless the request explicitly includes that action.
|
||||
|
||||
## Defaults
|
||||
|
||||
@@ -39,6 +43,8 @@ Require all of the following:
|
||||
- consistent public and OTA runtime versions across `app.json`, explicit
|
||||
`app.config.js` runtime, Android, and every iOS target
|
||||
(`scripts/set_version.mjs --check`);
|
||||
- `npm run release:preflight -w @sauci/mobile`, which also checks the iOS widget
|
||||
effective build settings and OTA certificate archive inclusion;
|
||||
- Node 20, JDK 17 for Android, and Xcode/CocoaPods for iOS;
|
||||
- the configured production API/Auth/RevenueCat environment, without printing
|
||||
secret values.
|
||||
@@ -80,6 +86,15 @@ Build from the clean prepared source using the package scripts in
|
||||
versioned artifact directory. When building both platforms, build Android first,
|
||||
then iOS.
|
||||
|
||||
For local EAS, retain the working directory and logs under a stable ignored path,
|
||||
set `EAS_LOCAL_BUILD_ARTIFACTS_DIR`, and preflight available disk space before
|
||||
starting. Set `EAS_LOCAL_BUILD_PLUGIN_PATH` only to the matching plugin
|
||||
executable `bin/run`, never a package directory or a hardcoded cache location.
|
||||
Classify plugin, archive, npm, and ENOSPC failures as pre-compilation failures;
|
||||
repair and preflight them without spending another build. Make at most one
|
||||
meaningful build for each newly proven input state. After a signing, profile, or
|
||||
capability failure, read back and prove the provider correction before rebuilding.
|
||||
|
||||
After each build:
|
||||
|
||||
1. Inspect the worktree before doing anything else. Accept only the documented
|
||||
@@ -88,15 +103,19 @@ After each build:
|
||||
dependency, or native-project regeneration as a failed build.
|
||||
2. Independently inspect the binary for package/bundle identifier, public
|
||||
version, platform build number, production endpoint configuration where
|
||||
inspectable, and signing identity/fingerprint. Require the embedded public
|
||||
version to equal the requested version.
|
||||
inspectable, signing identity/fingerprint, OTA runtime/channel/certificate,
|
||||
and SHA-256. Require the embedded public version to equal the requested
|
||||
version. For Android, also inspect all required ABIs, Worklets/Reanimated/Skia
|
||||
libraries, and the release permissions. For iOS, inspect every packaged
|
||||
extension after export.
|
||||
3. Record the verified platform build number in the new release-note entry.
|
||||
4. Commit only the expected metadata and release-note update as
|
||||
`chore(<platform>): record v<version> build <number>`. The worktree must be
|
||||
clean before starting the next platform build.
|
||||
|
||||
Do not accept a successful EAS exit code as proof of a valid artifact. If either
|
||||
requested platform fails, do not tag a partial release.
|
||||
Do not accept a successful EAS exit code as proof of a valid artifact. Quarantine
|
||||
or remove rejected artifacts so only accepted paths can be offered for upload. If
|
||||
either requested platform fails, do not tag a partial release.
|
||||
|
||||
## Tag and prove the result
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: release-mobile
|
||||
description: Prepare and verify Sauci iOS or Android releases. Use for versioning, EAS/native builds, Fastlane upload, release notes, or store submission preparation.
|
||||
description: Prepare, verify, and ship Sauci iOS or Android releases end to end. Use for versioning, EAS/native builds, store uploads, App Review / Play review submission, and rollout.
|
||||
---
|
||||
|
||||
# Mobile release
|
||||
@@ -9,6 +9,41 @@ Read `docs/releasing.md`, `apps/mobile/RELEASING.md` when present, and the mobil
|
||||
scoped instructions. Confirm target platform and release profile. Run repository
|
||||
verification plus a native acceptance pass before starting a release build.
|
||||
|
||||
## OTA update vs full store release
|
||||
|
||||
Choose the delivery mechanism before doing anything else; they are different
|
||||
pipelines with different blast radii. `docs/mobile-ota.md` is the authority.
|
||||
|
||||
- **OTA update** (`npm run ota:publish:<env>` in `apps/mobile`): ships only the
|
||||
JavaScript bundle and assets to builds already installed on devices. No store
|
||||
review, live within minutes, and rolled back by repointing the XPREM channel
|
||||
at a previous update. Valid only when the change touches no native code, no
|
||||
native dependency, no `ios/`/`android/` project files, and no config that
|
||||
alters the native binary. The OTA runtime version equals the public app
|
||||
version, so an OTA update can only reach installs of the current
|
||||
`expo.version`.
|
||||
- **Full store release** (this skill): a new signed binary through App Store
|
||||
Connect and Google Play, including review. Required whenever native code,
|
||||
native dependencies, the checked-in native projects, Expo SDK, permissions,
|
||||
entitlements, or `expo.version` change. Rollback is coarser: Play staged
|
||||
rollouts can be halted, iOS phased release can be paused, and a bad release is
|
||||
otherwise superseded by shipping the next version.
|
||||
|
||||
When a change qualifies for OTA, prefer OTA unless the user asks for a store
|
||||
release. Never present an OTA publish as a store release or vice versa; report
|
||||
which mechanism was used.
|
||||
|
||||
## Authorization
|
||||
|
||||
An explicit request for a mobile release authorizes the complete pipeline for
|
||||
the stated platforms: version bump, build, verification, store upload, review
|
||||
submission (App Review / Google Play review), and rollout — including full
|
||||
production rollout. Rollout is recoverable by design (staged-rollout halt,
|
||||
phased-release pause, OTA supersession, or the next store version), so do not
|
||||
pause to re-confirm these steps; execute them and report. A request that
|
||||
explicitly narrows scope (for example "upload only", "TestFlight only", or a
|
||||
named track) still bounds what you do.
|
||||
|
||||
Before a build from the current upstream branch, fetch, require a clean worktree,
|
||||
and prove `HEAD` matches its upstream. The `generate-release` skill may instead
|
||||
build a clean, scoped release-preparation commit that descends directly from that
|
||||
@@ -17,40 +52,65 @@ verified EAS version metadata. Sauci tracks its native iOS and Android projects,
|
||||
so routine release builds must package those checked-in sources. Do not run
|
||||
`expo prebuild` first; native regeneration is a separate reviewed change.
|
||||
|
||||
Use the package scripts in `apps/mobile/package.json`; they create artifacts only.
|
||||
Never infer upload or submission authority from build authorization. Store upload
|
||||
uses a separately authorized command.
|
||||
Run `npm run release:preflight -w @sauci/mobile` before allocating an EAS build.
|
||||
It rejects inconsistent public/runtime versions, an iOS widget build-setting
|
||||
override, and a public OTA certificate that would be omitted from the archive.
|
||||
For a local EAS build, also run `npm run release:preflight:local-eas -w
|
||||
@sauci/mobile` after setting `EAS_LOCAL_BUILD_PLUGIN_PATH` to the matching
|
||||
plugin's executable `bin/run`. Do not point it at the package directory or
|
||||
hardcode a machine-local cache path.
|
||||
|
||||
## Upload authorization gate
|
||||
Classify a failure before rebuilding. Archive, plugin-spawn, package-install, and
|
||||
disk-space failures happen before native compilation and must be repaired and
|
||||
preflighted without consuming another build. After a signing, profile, or
|
||||
capability failure, read back the provider state and prove only the affected
|
||||
input changed before rebuilding. Retain the EAS work directory, command log, and
|
||||
stable artifact path for every meaningful attempt. Never retry an ambiguous
|
||||
upload.
|
||||
|
||||
After an artifact passes verification, stop immediately before the external
|
||||
upload and ask the user for confirmation. The request must identify:
|
||||
Report provider progress by phase: packaging, native compilation, submission
|
||||
queue, or store processing. Do not describe every failure as a rebuild.
|
||||
|
||||
Use the package scripts in `apps/mobile/package.json` to build artifacts and the
|
||||
Fastlane lanes in `apps/mobile/fastlane/Fastfile` to upload, submit, and roll
|
||||
out.
|
||||
|
||||
## Upload and submission execution
|
||||
|
||||
Verification before upload is unchanged and mandatory. Independently verify and
|
||||
report:
|
||||
|
||||
- the platform, exact artifact path, bundle/package identifier, public version,
|
||||
build number, signature identity, and SHA-256 digest;
|
||||
- the exact destination and resulting state: Google Play internal-track draft or
|
||||
App Store Connect/TestFlight processing;
|
||||
- that the upload does not authorize a public rollout or App Review submission.
|
||||
- the exact destination and resulting state: which Play track and rollout
|
||||
status, or App Store Connect version state and whether App Review submission
|
||||
and automatic release are included.
|
||||
|
||||
For iOS, inspect the current App Store Connect automatic-distribution behavior
|
||||
before asking. If the build would be distributed automatically, name the exact
|
||||
TestFlight groups or testers in the request and include that effect in the fresh
|
||||
authorization. If the effect cannot be established, stop; changing an automatic-
|
||||
distribution setting also requires separate authorization.
|
||||
Once these details match the request, proceed through upload, review
|
||||
submission, and rollout without requesting further confirmation.
|
||||
|
||||
Even when the original request includes building, releasing, uploading, or
|
||||
submitting, require this fresh confirmation once the exact verified artifact and
|
||||
destination are known. End the request with: `Upload this verified artifact to
|
||||
<destination> now?` A clear affirmative response authorizes one upload of that
|
||||
artifact to that destination, plus only any automatic TestFlight distribution
|
||||
explicitly disclosed in the request. Run `eas submit` with the explicit
|
||||
`--platform`, `--profile production`, `--path`, and `--wait` arguments; never
|
||||
select `--latest`. If the result is ambiguous, inspect EAS and the store before
|
||||
doing anything else, do not retry, and ask again before another upload.
|
||||
- **Android**: `bundle exec fastlane android release aab:<absolute path>`
|
||||
commits the release on the production track (pass `track:` to target another
|
||||
track, `rollout:0.1` for a staged rollout). Play review runs automatically;
|
||||
rollout proceeds on approval. Use the `upload` lane only when the request is
|
||||
explicitly upload-only.
|
||||
- **iOS**: `bundle exec fastlane ios release ipa:<absolute path>` uploads,
|
||||
creates the App Store version, submits for App Review, and releases
|
||||
automatically on approval (pass `phased_release:true` for a 7-day phased
|
||||
release, `release_notes:"..."` for What's New). Use the `upload` lane for
|
||||
TestFlight-only requests. `eas submit` remains valid for upload-only flows;
|
||||
when used, pass explicit `--platform`, `--profile production`, `--path`, and
|
||||
`--wait` — never `--latest`.
|
||||
|
||||
Apply this gate to every upload method, including EAS, Fastlane, Xcode Organizer,
|
||||
and the store consoles. A later public rollout, App Review submission, or
|
||||
additional TestFlight distribution requires another fresh authorization.
|
||||
App Store Connect API group association does not establish the automatic-
|
||||
distribution switch. Verify that switch in an authenticated App Store Connect UI
|
||||
session before upload; if that UI verification is unavailable, stop rather
|
||||
than inferring the distribution effect from API data.
|
||||
|
||||
If an upload or submission command returns an ambiguous result, inspect EAS and
|
||||
the store before doing anything else; do not retry blind. After submission, read
|
||||
back the store state (Play Console track status / App Store Connect version
|
||||
state) and report it.
|
||||
|
||||
Preserve platform signing and local EAS versioning. After a build, verify that
|
||||
tracked changes are limited to the expected version metadata, then independently
|
||||
@@ -58,5 +118,30 @@ read the artifact version and signature. For an iOS IPA, inspect every packaged
|
||||
app extension as well as the containing app and require matching
|
||||
`CFBundleShortVersionString` and `CFBundleVersion` values. Treat Xcode archive
|
||||
warnings as diagnostic evidence, not the final artifact state: decide whether to
|
||||
reject or rebuild only after reading the exported IPA itself. Report the artifact,
|
||||
version/build number, upload result when authorized, and store read-back state.
|
||||
reject or rebuild only after reading the exported IPA itself. Report the
|
||||
artifact, version/build number, upload and submission results, rollout state,
|
||||
and store read-back state.
|
||||
|
||||
For Android, verify the accepted AAB has all four required ABIs, the Worklets,
|
||||
Reanimated, and Skia native libraries, no broad `READ_MEDIA_IMAGES` or
|
||||
`READ_MEDIA_VIDEO` permissions, and the production OTA runtime, channel, and
|
||||
public certificate. For iOS, verify the exported IPA, not archive warnings, and
|
||||
check both the containing app and every extension.
|
||||
|
||||
For the Android native dependency ordering already tracked in Gradle, preserve
|
||||
the explicit task dependency that makes Prefab inputs available before dependent
|
||||
native tasks. Fix task validation with ordering, not Firebase removal or
|
||||
dependency-version roulette.
|
||||
|
||||
After Android upload, EAS Submit `FINISHED`, the exact internal/DRAFT/no-rollout
|
||||
submit configuration, and the retained artifact digest are the available
|
||||
authoritative read-back when a direct Play track read would require creating an
|
||||
edit. Never create an edit merely to claim read-only verification.
|
||||
|
||||
For a checked-in iOS entitlement, inspect the real Apple App ID capability state
|
||||
before a signing rebuild. The capability sync bug tracked in
|
||||
https://github.com/expo/eas-cli/issues/3986 can report success while Apple
|
||||
rejects a malformed update. Read back Associated Domains, regenerate only the
|
||||
affected profile, and decode that profile to prove the entitlement before
|
||||
rebuilding. Noninteractive managed App Store Connect key auth may require
|
||||
`EXPO_APPLE_TEAM_ID`; preserve certificates and unrelated target profiles.
|
||||
@@ -2,6 +2,21 @@
|
||||
module.exports = {
|
||||
extends: 'expo',
|
||||
ignorePatterns: ['/dist/*'],
|
||||
rules: {
|
||||
// eslint-config-expo (SDK 56) pulls eslint-plugin-react-hooks v7, whose
|
||||
// recommended config adds React Compiler era rules as errors. Existing
|
||||
// patterns this codebase relies on deliberately (the "latest value" ref
|
||||
// pattern, effect-driven setState, dynamically created inline
|
||||
// components, and manual memoization) trip these new rules throughout
|
||||
// the app. Adopting the stricter rules is a deliberate follow-up
|
||||
// refactor, not an SDK upgrade side effect, so they are downgraded to
|
||||
// warnings here.
|
||||
'react-hooks/refs': 'warn',
|
||||
'react-hooks/set-state-in-effect': 'warn',
|
||||
'react-hooks/immutability': 'warn',
|
||||
'react-hooks/preserve-manual-memoization': 'warn',
|
||||
'react-hooks/static-components': 'warn',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['plugins/**/*.js'],
|
||||
|
||||
@@ -11,5 +11,6 @@ expo-env.d.ts
|
||||
*.keystore
|
||||
# Fastlane
|
||||
fastlane-key.json
|
||||
fastlane/.env
|
||||
fastlane/report.xml
|
||||
fastlane/README.md
|
||||
@@ -12,5 +12,8 @@ local.properties
|
||||
*.hprof
|
||||
.cxx/
|
||||
|
||||
# generated inline modules
|
||||
app/src/main/java/inline/
|
||||
|
||||
# Bundle artifacts
|
||||
*.jsbundle
|
||||
@@ -11,7 +11,7 @@ def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
||||
react {
|
||||
entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
|
||||
reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
||||
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
|
||||
hermesCommand = new File(["node", "--print", "require.resolve('hermes-compiler/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/hermesc/%OS-BIN%/hermesc"
|
||||
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
||||
|
||||
enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
|
||||
@@ -64,7 +64,7 @@ react {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true in release builds to optimize the app using R8.
|
||||
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
|
||||
*/
|
||||
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
|
||||
|
||||
@@ -92,8 +92,9 @@ android {
|
||||
applicationId 'com.sauci.app'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 49
|
||||
versionName "1.0.7"
|
||||
versionCode 51
|
||||
versionName "1.0.8"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
signingConfigs {
|
||||
@@ -182,53 +183,3 @@ dependencies {
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
|
||||
// Reanimated 4.1.7 imports Worklets from its pre-AGP-8 CMake output path,
|
||||
// while AGP 8.14 writes release outputs below intermediates/cxx/RelWithDebInfo.
|
||||
// Keep the checked-in Android project compatible with the supported SDK 54
|
||||
// dependency set by materializing that one legacy input after Worklets builds.
|
||||
def workletsReleaseAbis = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
|
||||
def syncWorkletsReleaseCmakeInputs = tasks.register("syncWorkletsReleaseCmakeInputs") {
|
||||
dependsOn(":react-native-worklets:externalNativeBuildRelease")
|
||||
|
||||
doLast {
|
||||
def workletsIntermediates = file("${rootDir}/../../../node_modules/react-native-worklets/android/build/intermediates")
|
||||
workletsReleaseAbis.each { abi ->
|
||||
def libraries = fileTree(workletsIntermediates) {
|
||||
include "cxx/RelWithDebInfo/**/obj/${abi}/libworklets.so"
|
||||
}.files
|
||||
if (libraries.size() != 1) {
|
||||
throw new GradleException("Expected exactly one Worklets release library for ${abi}, found ${libraries.size()}")
|
||||
}
|
||||
def source = libraries.iterator().next()
|
||||
def destination = file("${workletsIntermediates}/cmake/release/obj/${abi}/libworklets.so")
|
||||
destination.parentFile.mkdirs()
|
||||
java.nio.file.Files.deleteIfExists(destination.toPath())
|
||||
source.withInputStream { input ->
|
||||
destination.withOutputStream { output -> output << input }
|
||||
}
|
||||
if (destination.length() != source.length()) {
|
||||
throw new GradleException("Failed to copy Worklets release library for ${abi}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
def reanimatedProject = findProject(":react-native-reanimated")
|
||||
reanimatedProject?.tasks?.configureEach { task ->
|
||||
if (task.name == "externalNativeBuildRelease" || task.name == "prefabReleasePackage") {
|
||||
task.dependsOn(syncWorkletsReleaseCmakeInputs)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.configureEach { task ->
|
||||
if (task.name.startsWith("configureCMakeRelWithDebInfo")) {
|
||||
task.dependsOn(
|
||||
":react-native-worklets:prefabReleasePackage",
|
||||
":react-native-reanimated:prefabReleasePackage",
|
||||
":shopify_react-native-skia:prefabReleasePackage",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||
</manifest>
|
||||
@@ -1,7 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:replace="android:maxSdkVersion"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove"/>
|
||||
@@ -12,7 +12,7 @@
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:replace="android:maxSdkVersion"/>
|
||||
<uses-permission android:name="com.android.vending.BILLING"/>
|
||||
<queries>
|
||||
<package android:name="com.instagram.android"/>
|
||||
@@ -22,7 +22,7 @@
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:requestLegacyExternalStorage="true" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules" android:enableOnBackInvokedCallback="false">
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false" android:requestLegacyExternalStorage="true" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
@@ -30,13 +30,14 @@
|
||||
<meta-data android:name="expo.modules.updates.CODE_SIGNING_CERTIFICATE" android:value="-----BEGIN CERTIFICATE-----
MIICzTCCAbWgAwIBAgIJAPrPjxnUOGisMA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV
BAMTBVNhdWNpMB4XDTI2MDgzMDE4MTAzMFoXDTM2MDgzMDE4MTAzMFowEDEOMAwG
A1UEAxMFU2F1Y2kwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWiUac
8i9bIbNZjv8iunVr6kqlgb9vea98JHz/A83biyszg0+GpmLYsr4zV2F7A6RitiIx
SOML+wVedyD0F2quWccBGXinvMAds0TxgIOjQfP+8+VJ/duDjveHvn+OFW4s7XxW
SY7TK1ufDcPWo/f5XzfqtBveug49zv3/F3MwfmMUbEY/zvH7nayPGJNuSyf3wISn
JIt0BsHNhWykRXCzjaF2fy3nAebOyezbtbapCjUlxPENVtBCzBQ7C9kaQXCBw5mV
hxhJlOxHWXpHJgDLHpev8A/CWQRh67tsevjKi6R6SDlf0R0xSbDJTElx8goWOOQW
VO9aDqI+oH0KpWfnAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8E
DDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAyUc20bIRCxoBTJg84mPl
2nFDWSaAfzgo0K0b6x2T1suuq4LAOUgzBqJISrQAALd0besK1qUZSAMhjQu1wPZT
MHuWRMxQHfOkyHMYUfOoPMS4bE+4K0WjzfoVt3y1A+oxhcIAQUUGyNgNmebfEN07
JlX0B8UI8aIMjyfpsIDgZx1piHKwittGy/V4MPOLbmT2BBwS9BELcmWE4CwWbfeo
JM4lXY0hk3M/GTstRYaUKiwBWEU+z1PdAmWAgrtxpVnXN+oaToWh0WbVkbF1K6r5
3oBGFTL7kT+Wogg9GK04XoAREw0jZJ/k77oJx68lGVBNiB+Ue2KqX0H/0cf2CslY
Ug==
-----END CERTIFICATE-----
"/>
|
||||
<meta-data android:name="expo.modules.updates.CODE_SIGNING_METADATA" android:value="{"keyid":"main","alg":"rsa-v1_5-sha256"}"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLE_BSDIFF_PATCH_SUPPORT" android:value="true"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="@string/expo_runtime_version"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="5000"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://ota.apps.jbrunton.co.uk/manifest"/>
|
||||
<meta-data android:name="expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY" android:value="{"expo-channel-name":"production","expo-app-id":"9a66f3c2-fda7-416f-87e2-6db833b32e9d","xprem-branch":""}"/>
|
||||
<service android:name="com.reactnativeandroidwidget.RNWidgetCollectionService" android:permission="android.permission.BIND_REMOTEVIEWS"/>
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|smallestScreenSize|assetsPaths" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
@@ -46,7 +47,6 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="app.sauci"/>
|
||||
<data android:scheme="com.sauci.app"/>
|
||||
<data android:scheme="exp+sauci"/>
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true" data-generated="true">
|
||||
@@ -56,7 +56,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<receiver android:name=".widget.LiveDraw" android:exported="true" android:label="Live Draw">
|
||||
<receiver android:name=".widget.LiveDraw" android:exported="false" android:label="Live Draw">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
<action android:name="com.sauci.app.WIDGET_CLICK"/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.sauci.app
|
||||
import expo.modules.splashscreen.SplashScreenManager
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -15,7 +16,10 @@ class MainActivity : ReactActivity() {
|
||||
// Set the theme to AppTheme BEFORE onCreate to support
|
||||
// coloring the background, status bar, and navigation bar.
|
||||
// This is required for expo-splash-screen.
|
||||
setTheme(R.style.AppTheme);
|
||||
// setTheme(R.style.AppTheme);
|
||||
// @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af
|
||||
SplashScreenManager.registerOnActivity(this)
|
||||
// @generated end expo-splashscreen
|
||||
super.onCreate(null)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,40 +5,27 @@ import android.content.res.Configuration
|
||||
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactNativeHost
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||
import com.facebook.react.common.ReleaseLevel
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
import expo.modules.ExpoReactHostFactory
|
||||
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
|
||||
this,
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> {
|
||||
val packages = PackageList(this).packages
|
||||
override val reactHost: ReactHost by lazy {
|
||||
ExpoReactHostFactory.getDefaultReactHost(
|
||||
context = applicationContext,
|
||||
packageList =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(new MyReactNativePackage());
|
||||
return packages
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
||||
|
||||
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
||||
|
||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
)
|
||||
|
||||
override val reactHost: ReactHost
|
||||
get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@@ -1,84 +1,6 @@
|
||||
package com.sauci.app.widget;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Base64;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.RemoteViews;
|
||||
import com.reactnativeandroidwidget.RNWidgetProvider;
|
||||
|
||||
import com.sauci.app.R;
|
||||
|
||||
public class LiveDraw extends AppWidgetProvider {
|
||||
|
||||
private static final String PREFS_NAME_SUFFIX = ".widgetdata";
|
||||
private static final String KEY_IMAGE = "LiveDrawImage";
|
||||
private static final int BG_COLOR = 0xFF1A1A2E;
|
||||
private static final int CORNER_RADIUS_DP = 16;
|
||||
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
for (int widgetId : appWidgetIds) {
|
||||
updateWidget(context, appWidgetManager, widgetId);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWidget(Context context, AppWidgetManager appWidgetManager, int widgetId) {
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_live_draw);
|
||||
|
||||
String base64 = getStoredImage(context);
|
||||
if (base64 != null && !base64.isEmpty()) {
|
||||
try {
|
||||
byte[] bytes = Base64.decode(base64, Base64.DEFAULT);
|
||||
Bitmap decoded = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
if (decoded != null) {
|
||||
Bitmap rounded = addRoundedBackground(context, decoded);
|
||||
views.setImageViewBitmap(R.id.widget_image, rounded);
|
||||
decoded.recycle();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
setPlaceholder(views);
|
||||
}
|
||||
} else {
|
||||
setPlaceholder(views);
|
||||
}
|
||||
|
||||
appWidgetManager.updateAppWidget(widgetId, views);
|
||||
}
|
||||
|
||||
private String getStoredImage(Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(
|
||||
context.getPackageName() + PREFS_NAME_SUFFIX, Context.MODE_PRIVATE);
|
||||
return prefs.getString(KEY_IMAGE, null);
|
||||
}
|
||||
|
||||
private void setPlaceholder(RemoteViews views) {
|
||||
views.setImageViewResource(R.id.widget_image, R.drawable.widget_placeholder);
|
||||
}
|
||||
|
||||
private Bitmap addRoundedBackground(Context context, Bitmap drawing) {
|
||||
float radius = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS_DP,
|
||||
context.getResources().getDisplayMetrics());
|
||||
|
||||
int w = drawing.getWidth();
|
||||
int h = drawing.getHeight();
|
||||
|
||||
Bitmap output = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(output);
|
||||
|
||||
Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
bgPaint.setColor(BG_COLOR);
|
||||
canvas.drawRoundRect(new RectF(0, 0, w, h), radius, radius, bgPaint);
|
||||
|
||||
canvas.drawBitmap(drawing, 0, 0, null);
|
||||
|
||||
return output;
|
||||
}
|
||||
public class LiveDraw extends RNWidgetProvider {
|
||||
}
|
||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 24 KiB |
@@ -2,6 +2,5 @@
|
||||
<color name="splashscreen_background">#1a1a2e</color>
|
||||
<color name="iconBackground">#1a1a2e</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
<color name="colorPrimaryDark">#1a1a2e</color>
|
||||
<color name="notification_icon_color">#e94560</color>
|
||||
</resources>
|
||||
@@ -1,7 +1,7 @@
|
||||
<resources>
|
||||
<string name="app_name">Sauci</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
|
||||
<string name="expo_runtime_version">1.0.8</string>
|
||||
<string name="widget_livedraw_description" translatable="false">See your shared drawing with your partner.</string>
|
||||
<string name="expo_runtime_version">1.0.7</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
</resources>
|
||||
@@ -1,18 +1,14 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:editTextStyle">@style/ResetEditText</item>
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:statusBarColor">#1a1a2e</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="ResetEditText" parent="@android:style/Widget.EditText">
|
||||
<item name="android:padding">0dp</item>
|
||||
<item name="android:textColorHint">#c8c8c8</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
</style>
|
||||
<style name="Theme.App.SplashScreen" parent="AppTheme">
|
||||
<item name="android:windowBackground">@drawable/ic_launcher_background</item>
|
||||
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
<item name="android:windowSplashScreenBehavior">icon_preferred</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -4,10 +4,18 @@
|
||||
android:minHeight="110dp"
|
||||
android:targetCellWidth="4"
|
||||
android:targetCellHeight="2"
|
||||
|
||||
|
||||
|
||||
android:resizeMode="none"
|
||||
|
||||
android:description="@string/widget_livedraw_description"
|
||||
android:initialLayout="@layout/widget_live_draw"
|
||||
|
||||
android:initialLayout="@layout/rn_widget"
|
||||
android:previewImage="@drawable/livedraw_preview"
|
||||
|
||||
|
||||
|
||||
android:updatePeriodMillis="1800000"
|
||||
android:widgetCategory="home_screen">
|
||||
</appwidget-provider>
|
||||
@@ -7,16 +7,13 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.6'
|
||||
classpath 'com.google.gms:google-services:4.4.1'
|
||||
classpath 'com.google.gms:google-services:4.4.4'
|
||||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "expo-root-project"
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
@@ -24,3 +21,6 @@ allprojects {
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "expo-root-project"
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
@@ -10,12 +10,12 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
@@ -41,6 +41,11 @@ newArchEnabled=true
|
||||
# If set to false, you will be using JSC instead.
|
||||
hermesEnabled=true
|
||||
|
||||
# Use this property to enable edge-to-edge display support.
|
||||
# This allows your app to draw behind system bars for an immersive UI.
|
||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||
edgeToEdgeEnabled=true
|
||||
|
||||
# Enable GIF support in React Native images (~200 B increase)
|
||||
expo.gif.enabled=true
|
||||
# Enable webp support in React Native images (~85 KB increase)
|
||||
@@ -55,16 +60,7 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||
expo.useLegacyPackaging=false
|
||||
|
||||
android.targetSdkVersion=36
|
||||
android.extraMavenRepos=[]
|
||||
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||
expo.edgeToEdgeEnabled=true
|
||||
|
||||
# Use this property to enable edge-to-edge display support.
|
||||
# This allows your app to draw behind system bars for an immersive UI.
|
||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||
edgeToEdgeEnabled=true
|
||||
|
||||
expo.inlineModules.watchedDirectories=[]
|
||||
android.compileSdkVersion=36
|
||||
android.targetSdkVersion=36
|
||||
android.buildToolsVersion=36.0.0
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -86,8 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -115,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -173,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -206,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
@REM Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
@REM
|
||||
@REM This source code is licensed under the MIT license found in the
|
||||
@REM LICENSE file in the root directory of this source tree.
|
||||
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@@ -70,11 +75,10 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -29,10 +29,10 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
||||
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
|
||||
}
|
||||
}
|
||||
expoAutolinking.useExpoModules()
|
||||
|
||||
rootProject.name = 'Sauci'
|
||||
|
||||
expoAutolinking.useExpoModules()
|
||||
expoAutolinking.useExpoVersionCatalog()
|
||||
|
||||
include ':app'
|
||||
|
||||
@@ -81,7 +81,7 @@ module.exports = () => ({
|
||||
// Keep this explicit because EOAS validates a primitive runtime string before
|
||||
// upload. The release version script updates it atomically with app.json and
|
||||
// the checked-in native runtime values.
|
||||
runtimeVersion: '1.0.7',
|
||||
runtimeVersion: '1.0.8',
|
||||
updates: {
|
||||
enabled: true,
|
||||
url: OTA_MANIFEST_URL,
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
"expo": {
|
||||
"name": "Sauci",
|
||||
"slug": "sauci",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"scheme": "app.sauci",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": true,
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#1a1a2e"
|
||||
},
|
||||
"platforms": [
|
||||
"ios",
|
||||
"android"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": false,
|
||||
"bundleIdentifier": "com.sauci.app",
|
||||
@@ -27,7 +25,7 @@
|
||||
"group.com.sauci.app"
|
||||
]
|
||||
},
|
||||
"buildNumber": "46",
|
||||
"buildNumber": "47",
|
||||
"usesAppleSignIn": true,
|
||||
"googleServicesFile": "./GoogleService-Info.plist",
|
||||
"infoPlist": {
|
||||
@@ -189,7 +187,7 @@
|
||||
"backgroundColor": "#1a1a2e"
|
||||
},
|
||||
"package": "com.sauci.app",
|
||||
"versionCode": 49,
|
||||
"versionCode": 51,
|
||||
"googleServicesFile": "./google-services.json",
|
||||
"permissions": [
|
||||
"RECEIVE_BOOT_COMPLETED",
|
||||
@@ -223,6 +221,14 @@
|
||||
},
|
||||
"plugins": [
|
||||
"./plugins/withFixHoisting.js",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
"image": "./assets/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#1a1a2e"
|
||||
}
|
||||
],
|
||||
"@react-native-firebase/app",
|
||||
[
|
||||
"expo-build-properties",
|
||||
@@ -320,7 +326,10 @@
|
||||
"origin": false
|
||||
},
|
||||
"eas": {
|
||||
"projectId": "807ca0af-e9bd-40e8-a658-f3382072c724"
|
||||
"projectId": "807ca0af-e9bd-40e8-a658-f3382072c724",
|
||||
"observe": {
|
||||
"endpointUrl": "https://ota.apps.jbrunton.co.uk/observe/9a66f3c2-fda7-416f-87e2-6db833b32e9d"
|
||||
}
|
||||
}
|
||||
},
|
||||
"owner": "jjbrunton-apps"
|
||||
|
||||
@@ -389,7 +389,7 @@ const styles = StyleSheet.create({
|
||||
zIndex: 100,
|
||||
},
|
||||
navBarBackground: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
backgroundColor: colors.background,
|
||||
|
||||
@@ -5,6 +5,11 @@ import "../src/polyfills/web";
|
||||
import { initCrashlytics, setUserContext, clearUserContext } from "../src/lib/crashlytics";
|
||||
initCrashlytics();
|
||||
|
||||
// Install the Observe global JS error handler after Crashlytics so it wraps
|
||||
// (and always calls) whatever handler is already registered.
|
||||
import { installObserveGlobalErrorHandler, logAppStarted } from "../src/lib/observe";
|
||||
installObserveGlobalErrorHandler();
|
||||
|
||||
// Analytics imports (initialization deferred until app mounts)
|
||||
import { initAnalytics, setUserId, clearUserId, logScreenView, Events } from "../src/lib/analytics";
|
||||
|
||||
@@ -73,6 +78,7 @@ export default function RootLayout() {
|
||||
analyticsInitialized.current = true;
|
||||
initAnalytics();
|
||||
Events.appOpened("cold");
|
||||
logAppStarted();
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -425,7 +425,7 @@ const styles = StyleSheet.create({
|
||||
zIndex: 100,
|
||||
},
|
||||
navBarBackground: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
json_key_file(ENV["GOOGLE_PLAY_KEY_FILE"] || File.expand_path("../fastlane-key.json", __dir__))
|
||||
package_name("com.sauci.app")
|
||||
app_identifier("com.sauci.app")
|
||||
@@ -45,4 +45,86 @@ platform :android do
|
||||
lane :upload_beta do |options|
|
||||
upload(options.merge(track: "beta"))
|
||||
end
|
||||
|
||||
desc "Upload AAB and release it: commits the release on the given track (default production), triggering Play review and rollout on approval"
|
||||
lane :release do |options|
|
||||
track = options[:track] || "production"
|
||||
|
||||
project_root = File.expand_path("..", __dir__)
|
||||
aab_path = options[:aab]
|
||||
UI.user_error!("Pass the exact verified artifact with aab: — never release an implicit build") unless aab_path
|
||||
aab_path = File.expand_path(aab_path, project_root) unless aab_path.start_with?("/")
|
||||
UI.user_error!("AAB not found at #{aab_path}") unless File.exist?(aab_path)
|
||||
|
||||
rollout = options[:rollout] # e.g. "0.1" for 10% staged rollout; omit for full rollout
|
||||
|
||||
UI.message("Releasing #{aab_path} to #{track} track#{rollout ? " at #{(rollout.to_f * 100).round}% rollout" : " at full rollout"}...")
|
||||
|
||||
upload_to_play_store(
|
||||
track: track,
|
||||
aab: aab_path,
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true,
|
||||
skip_upload_changelogs: false,
|
||||
release_status: rollout ? "inProgress" : "completed",
|
||||
rollout: rollout,
|
||||
)
|
||||
|
||||
UI.success("Release committed to #{track}. Google Play review runs automatically; rollout proceeds on approval.")
|
||||
end
|
||||
end
|
||||
|
||||
platform :ios do
|
||||
# App Store Connect API key, supplied via environment (never committed):
|
||||
# ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_PATH (path to the .p8 file)
|
||||
private_lane :asc_api_key do
|
||||
UI.user_error!("Set ASC_KEY_ID, ASC_ISSUER_ID, and ASC_KEY_PATH") unless ENV["ASC_KEY_ID"] && ENV["ASC_ISSUER_ID"] && ENV["ASC_KEY_PATH"]
|
||||
app_store_connect_api_key(
|
||||
key_id: ENV["ASC_KEY_ID"],
|
||||
issuer_id: ENV["ASC_ISSUER_ID"],
|
||||
key_filepath: ENV["ASC_KEY_PATH"],
|
||||
)
|
||||
end
|
||||
|
||||
desc "Upload IPA to App Store Connect / TestFlight only (no App Review submission)"
|
||||
lane :upload do |options|
|
||||
ipa_path = options[:ipa]
|
||||
UI.user_error!("Pass the exact verified artifact with ipa:") unless ipa_path && File.exist?(File.expand_path(ipa_path))
|
||||
|
||||
upload_to_testflight(
|
||||
api_key: asc_api_key,
|
||||
ipa: File.expand_path(ipa_path),
|
||||
skip_waiting_for_build_processing: false,
|
||||
)
|
||||
UI.success("Uploaded to App Store Connect; build is processing for TestFlight.")
|
||||
end
|
||||
|
||||
desc "Upload IPA, create the App Store version, and submit it for App Review with automatic release on approval"
|
||||
lane :release do |options|
|
||||
ipa_path = options[:ipa]
|
||||
UI.user_error!("Pass the exact verified artifact with ipa:") unless ipa_path && File.exist?(File.expand_path(ipa_path))
|
||||
|
||||
api_key = asc_api_key
|
||||
|
||||
upload_to_app_store(
|
||||
api_key: api_key,
|
||||
ipa: File.expand_path(ipa_path),
|
||||
submit_for_review: true,
|
||||
automatic_release: options.fetch(:automatic_release, true),
|
||||
phased_release: options.fetch(:phased_release, false),
|
||||
precheck_include_in_app_purchases: false,
|
||||
# Metadata lives in App Store Connect; only release notes change per version.
|
||||
skip_metadata: options[:release_notes].nil?,
|
||||
skip_screenshots: true,
|
||||
release_notes: options[:release_notes] ? { "en-GB" => options[:release_notes], "default" => options[:release_notes] } : nil,
|
||||
submission_information: {
|
||||
add_id_info_uses_idfa: false,
|
||||
export_compliance_uses_encryption: false,
|
||||
},
|
||||
force: true, # skip interactive HTML preview; agents run non-interactively
|
||||
)
|
||||
|
||||
UI.success("Submitted for App Review. The version releases #{options.fetch(:automatic_release, true) ? "automatically on approval" : "manually after approval"}.")
|
||||
end
|
||||
end
|
||||
@@ -28,3 +28,6 @@ project.xcworkspace
|
||||
|
||||
# CocoaPods
|
||||
/Pods/
|
||||
|
||||
# @bacons/apple-targets generated entitlements cache
|
||||
/.targets/
|
||||
@@ -1,15 +1,28 @@
|
||||
# Set by expo-router. This enables Fabric-only features from react-native-screens
|
||||
ENV['RNS_GAMMA_ENABLED'] ||= '1'
|
||||
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']
|
||||
def ccache_enabled?(podfile_properties)
|
||||
# Environment variable takes precedence
|
||||
return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE']
|
||||
|
||||
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
|
||||
install! 'cocoapods',
|
||||
:deterministic_uuids => false
|
||||
# Fall back to Podfile properties
|
||||
podfile_properties['apple.ccacheEnabled'] == 'true'
|
||||
end
|
||||
|
||||
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
|
||||
ENV['RCT_USE_RN_DEP'] ||= podfile_properties['ios.buildReactNativeFromSource'] == 'true' ? '0' : '1'
|
||||
ENV['RCT_USE_PREBUILT_RNCORE'] ||= podfile_properties['ios.buildReactNativeFromSource'] == 'true' ? '0' : '1'
|
||||
ENV['RCT_HERMES_V1_ENABLED'] ||= '0' if podfile_properties['expo.useHermesV1'] == 'false'
|
||||
# An explicit `ios.usePrecompiledModules: false` must win even when the env var is already set
|
||||
# (e.g. EXPO_USE_PRECOMPILED_MODULES exported by EAS Build), so force-disable here before the default.
|
||||
ENV['EXPO_USE_PRECOMPILED_MODULES'] = '0' if podfile_properties['EXPO_USE_PRECOMPILED_MODULES'] == 'false'
|
||||
ENV['EXPO_USE_PRECOMPILED_MODULES'] ||= '1'
|
||||
platform :ios, podfile_properties['ios.deploymentTarget'] || '16.4'
|
||||
|
||||
prepare_react_native_project!
|
||||
|
||||
@@ -23,7 +36,8 @@ target 'Sauci' do
|
||||
'node',
|
||||
'--no-warnings',
|
||||
'--eval',
|
||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
||||
'require(\'expo/bin/autolinking\')',
|
||||
'expo-modules-autolinking',
|
||||
'react-native-config',
|
||||
'--json',
|
||||
'--platform',
|
||||
@@ -49,34 +63,8 @@ target 'Sauci' do
|
||||
installer,
|
||||
config[:reactNativePath],
|
||||
:mac_catalyst_enabled => false,
|
||||
:ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
|
||||
:ccache_enabled => ccache_enabled?(podfile_properties),
|
||||
)
|
||||
|
||||
# The fmt version bundled with React Native 0.76 enables consteval for
|
||||
# Apple Clang, but Apple Clang 21 (Xcode 26.4+) rejects fmt's FMT_STRING
|
||||
# call sites. Patch the generated pod until React Native supplies a newer
|
||||
# fmt release. Keeping this here makes clean pod installs reproducible.
|
||||
fmt_base_header = File.join(__dir__, 'Pods', 'fmt', 'include', 'fmt', 'base.h')
|
||||
if File.exist?(fmt_base_header)
|
||||
fmt_base_source = File.read(fmt_base_header)
|
||||
fmt_base_source.sub!(
|
||||
/#elif defined\(__apple_build_version__\) && __apple_build_version__ < 14000029L/,
|
||||
'#elif defined(__apple_build_version__)'
|
||||
)
|
||||
File.chmod(0644, fmt_base_header)
|
||||
File.write(fmt_base_header, fmt_base_source)
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"expo.jsEngine": "hermes",
|
||||
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
|
||||
"newArchEnabled": "true",
|
||||
"ios.useFrameworks": "static",
|
||||
"apple.extraPods": "[]",
|
||||
"apple.ccacheEnabled": "false",
|
||||
"apple.privacyManifestAggregationEnabled": "true"
|
||||
"expo.inlineModules.watchedDirectories": "[]",
|
||||
"expo.inlineModules.xcodeProjectTargets": "{\"mainTarget\":\"Sauci\",\"targets\":[]}",
|
||||
"ios.forceStaticLinking": "[]",
|
||||
"apple.privacyManifestAggregationEnabled": "true",
|
||||
"EXPO_USE_PRECOMPILED_MODULES": "true",
|
||||
"ios.useFrameworks": "static"
|
||||
}
|
||||
@@ -3,19 +3,18 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
|
||||
5254BDADE70741A0B2504FB1 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = F744EAD4ABA54509966475D6 /* noop-file.swift */; };
|
||||
7B71EEB2AF682FA7AFF556D5 /* Pods_Sauci.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDDAD8C12C38D3BD0A76D345 /* Pods_Sauci.framework */; };
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
|
||||
3F202C2A859975F48A83ADD7 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 779267BD380A5D29590F1BDC /* ExpoModulesProvider.swift */; };
|
||||
45941E25D336B6152BFCEFB9 /* Pods_Sauci.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B482C7D915C22B3F33CA9841 /* Pods_Sauci.framework */; };
|
||||
804CD03F6B584D08B7803C63 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 69E91809C21A4977A3249D53 /* PrivacyInfo.xcprivacy */; };
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
||||
CEF2F278ABBC5FBFD470A0F7 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7B5C17768E3B74AC152531F /* PrivacyInfo.xcprivacy */; };
|
||||
D60EEEF252214D45B67A0063 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D58CE235EF5F40F58FB8111D /* GoogleService-Info.plist */; };
|
||||
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
|
||||
FDFABE1A1A3547B1B22DD2DD /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A9280FDF3E0D43AC90382DBC /* GoogleService-Info.plist */; };
|
||||
XX12B55A1EDABF083C3B51XX /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = XX470101BAEB8E0BBC8B0DXX /* WidgetKit.framework */; };
|
||||
XX1A6800511A685AB346D2XX /* AppIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = XXA12889081EB894481655XX /* AppIntents.framework */; };
|
||||
XX3C562DEF346B45594832XX /* ActivityKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = XXCAF23C3FC41F82140FA1XX /* ActivityKit.framework */; };
|
||||
@@ -24,11 +23,11 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
XXA7C7D5580082A497FD95XX /* PBXContainerItemProxy */ = {
|
||||
XX77D941E1DB38490B8F4EXX /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = XX540FB4ABC75CE91A6036XX;
|
||||
remoteGlobalIDString = XXB3405CC208C2ECA4797FXX;
|
||||
remoteInfo = LiveDrawWidget;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
@@ -51,18 +50,17 @@
|
||||
13B07F961A680F5B00A75B9A /* Sauci.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; fileEncoding = 4; includeInIndex = 0; path = Sauci.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Sauci/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sauci/Info.plist; sourceTree = "<group>"; };
|
||||
6C2E3173556A471DD304B334 /* Pods-Sauci.debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sauci.debug.xcconfig"; path = "Target Support Files/Pods-Sauci/Pods-Sauci.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7A4D352CD337FB3A3BF06240 /* Pods-Sauci.release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sauci.release.xcconfig"; path = "Target Support Files/Pods-Sauci/Pods-Sauci.release.xcconfig"; sourceTree = "<group>"; };
|
||||
A9280FDF3E0D43AC90382DBC /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Sauci/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
69E91809C21A4977A3249D53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = PrivacyInfo.xcprivacy; path = Sauci/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
779267BD380A5D29590F1BDC /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Sauci/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
A172EA788EF476A7083810FE /* Pods-Sauci.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sauci.release.xcconfig"; path = "Target Support Files/Pods-Sauci/Pods-Sauci.release.xcconfig"; sourceTree = "<group>"; };
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Sauci/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||
B482C7D915C22B3F33CA9841 /* Pods_Sauci.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Sauci.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
D7B5C17768E3B74AC152531F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 1; lastKnownFileType = undefined; name = PrivacyInfo.xcprivacy; path = Sauci/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
DA536CAC88B349A59DFC1F1E /* Sauci-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Sauci-Bridging-Header.h"; path = "Sauci/Sauci-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
DDDAD8C12C38D3BD0A76D345 /* Pods_Sauci.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; fileEncoding = 4; includeInIndex = undefined; path = Pods_Sauci.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D58CE235EF5F40F58FB8111D /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Sauci/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = undefined; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Sauci/AppDelegate.swift; sourceTree = "<group>"; };
|
||||
F744EAD4ABA54509966475D6 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Sauci/noop-file.swift"; sourceTree = "<group>"; };
|
||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Sauci/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
F11748442D0722820044C1D9 /* Sauci-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Sauci-Bridging-Header.h"; path = "Sauci/Sauci-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
FEACE2E5B4EA50AFBF4DDF34 /* Pods-Sauci.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sauci.debug.xcconfig"; path = "Target Support Files/Pods-Sauci/Pods-Sauci.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
XX470101BAEB8E0BBC8B0DXX /* WidgetKit.framework */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = undefined; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
XX4DFF38D47332D6BF0183XX /* SwiftUI.framework */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = undefined; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
XXA12889081EB894481655XX /* AppIntents.framework */ = {isa = PBXFileReference; fileEncoding = 4; includeInIndex = undefined; lastKnownFileType = wrapper.framework; name = AppIntents.framework; path = System/Library/Frameworks/AppIntents.framework; sourceTree = SDKROOT; };
|
||||
@@ -71,21 +69,21 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
XX020388E3BB9C764E774FXX /* Exceptions for "widget" folder in "LiveDrawWidget" target */ = {
|
||||
XX6FB9E6678087E3AAC658XX /* Exceptions for "widget" folder in "LiveDrawWidget" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
"expo-target.config.js",
|
||||
);
|
||||
target = XX540FB4ABC75CE91A6036XX /* LiveDrawWidget */;
|
||||
target = XXB3405CC208C2ECA4797FXX /* LiveDrawWidget */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
XX683E29AD7AD8279E17B2XX /* widget */ = {
|
||||
XXBB44BF07EB68666150ABXX /* widget */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
XX020388E3BB9C764E774FXX /* Exceptions for "widget" folder in "LiveDrawWidget" target */,
|
||||
XX6FB9E6678087E3AAC658XX /* Exceptions for "widget" folder in "LiveDrawWidget" target */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
@@ -101,7 +99,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7B71EEB2AF682FA7AFF556D5 /* Pods_Sauci.framework in Frameworks */,
|
||||
45941E25D336B6152BFCEFB9 /* Pods_Sauci.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -122,15 +120,32 @@
|
||||
13B07FAE1A68108700A75B9A /* Sauci */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BB2F792B24A3F905000567C9 /* Supporting */,
|
||||
F11748412D0307B40044C1D9 /* AppDelegate.swift */,
|
||||
F11748442D0722820044C1D9 /* Sauci-Bridging-Header.h */,
|
||||
BB2F792B24A3F905000567C9 /* Supporting */,
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
||||
A9280FDF3E0D43AC90382DBC /* GoogleService-Info.plist */,
|
||||
F744EAD4ABA54509966475D6 /* noop-file.swift */,
|
||||
DA536CAC88B349A59DFC1F1E /* Sauci-Bridging-Header.h */,
|
||||
D7B5C17768E3B74AC152531F /* PrivacyInfo.xcprivacy */,
|
||||
69E91809C21A4977A3249D53 /* PrivacyInfo.xcprivacy */,
|
||||
D58CE235EF5F40F58FB8111D /* GoogleService-Info.plist */,
|
||||
);
|
||||
name = Sauci;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1957149DB83AAF8603C60859 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FEACE2E5B4EA50AFBF4DDF34 /* Pods-Sauci.debug.xcconfig */,
|
||||
A172EA788EF476A7083810FE /* Pods-Sauci.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2BFCD64E7EEB594CC2D9E261 /* Sauci */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
779267BD380A5D29590F1BDC /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = Sauci;
|
||||
sourceTree = "<group>";
|
||||
@@ -141,9 +156,9 @@
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
XX470101BAEB8E0BBC8B0DXX /* WidgetKit.framework */,
|
||||
XX4DFF38D47332D6BF0183XX /* SwiftUI.framework */,
|
||||
DDDAD8C12C38D3BD0A76D345 /* Pods_Sauci.framework */,
|
||||
XXCAF23C3FC41F82140FA1XX /* ActivityKit.framework */,
|
||||
XXA12889081EB894481655XX /* AppIntents.framework */,
|
||||
B482C7D915C22B3F33CA9841 /* Pods_Sauci.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -163,8 +178,8 @@
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
D65327D7A22EEC0BE12398D9 /* Pods */,
|
||||
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */,
|
||||
1957149DB83AAF8603C60859 /* Pods */,
|
||||
CC8FEB5C5178B9E5E71D4F61 /* ExpoModulesProviders */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
@@ -180,14 +195,6 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
92DBD88DE9BF7D494EA9DA96 /* Sauci */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = Sauci;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BB2F792B24A3F905000567C9 /* Supporting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -197,19 +204,10 @@
|
||||
path = Sauci/Supporting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D65327D7A22EEC0BE12398D9 /* Pods */ = {
|
||||
CC8FEB5C5178B9E5E71D4F61 /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6C2E3173556A471DD304B334 /* Pods-Sauci.debug.xcconfig */,
|
||||
7A4D352CD337FB3A3BF06240 /* Pods-Sauci.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
92DBD88DE9BF7D494EA9DA96 /* Sauci */,
|
||||
2BFCD64E7EEB594CC2D9E261 /* Sauci */,
|
||||
);
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
@@ -217,7 +215,7 @@
|
||||
XX3E41C1850246162C0061XX /* expo:targets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
XX683E29AD7AD8279E17B2XX /* widget */,
|
||||
XXBB44BF07EB68666150ABXX /* widget */,
|
||||
);
|
||||
name = "expo:targets";
|
||||
path = ../targets;
|
||||
@@ -231,30 +229,31 @@
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Sauci" */;
|
||||
buildPhases = (
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
||||
DC3D179C6B62F16DA5F17C2F /* [Expo] Configure project */,
|
||||
E6F7433229E3C1448FC9F062 /* [Expo] Configure project */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||
D58CF933D4804A0CB49C2891 /* [Expo Dev Launcher] Strip Local Network Keys for Release */,
|
||||
XX72A8CFBF0FBA13A79902XX /* Embed Foundation Extensions */,
|
||||
A1248897D24903117793599B /* [CP] Embed Pods Frameworks */,
|
||||
FB4B019D835055733ACDE109 /* [CP-User] [RNFB] Core Configuration */,
|
||||
563ACE2651AC39438F3B399B /* [CP-User] [RNFB] Crashlytics Configuration */,
|
||||
503A0FD3DBA862B4BE649BBB /* [CP] Embed Pods Frameworks */,
|
||||
8490E10253F91A7BEB80F555 /* [CP-User] [RNFB] Core Configuration */,
|
||||
0EE1B67C9AAD8BD8562E800E /* [CP-User] [RNFB] Crashlytics Configuration */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
XX97289CEC7994AD5F4142XX /* PBXTargetDependency */,
|
||||
XX5D3A21868E45E6E5BDC3XX /* PBXTargetDependency */,
|
||||
);
|
||||
name = Sauci;
|
||||
productName = Sauci;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* Sauci.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
XX540FB4ABC75CE91A6036XX /* LiveDrawWidget */ = {
|
||||
XXB3405CC208C2ECA4797FXX /* LiveDrawWidget */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = XX63FAB1BD532DA0D8AE18XX /* Build configuration list for PBXNativeTarget "LiveDrawWidget" */;
|
||||
buildConfigurationList = XX35D62F4FBF2C9086E8C9XX /* Build configuration list for PBXNativeTarget "LiveDrawWidget" */;
|
||||
buildPhases = (
|
||||
XX074B8811C1CF8E8A3D74XX /* Frameworks */,
|
||||
XXE45443DC38DCFE1DB622XX /* Sources */,
|
||||
@@ -265,7 +264,7 @@
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
XX683E29AD7AD8279E17B2XX /* widget */,
|
||||
XXBB44BF07EB68666150ABXX /* widget */,
|
||||
);
|
||||
name = LiveDrawWidget;
|
||||
productName = LiveDrawWidget;
|
||||
@@ -283,7 +282,7 @@
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1250;
|
||||
};
|
||||
XX540FB4ABC75CE91A6036XX = {
|
||||
XXB3405CC208C2ECA4797FXX = {
|
||||
CreatedOnToolsVersion = 14.3;
|
||||
DevelopmentTeam = BXVPBATDZY;
|
||||
ProvisioningStyle = Automatic;
|
||||
@@ -304,7 +303,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* Sauci */,
|
||||
XX540FB4ABC75CE91A6036XX /* LiveDrawWidget */,
|
||||
XXB3405CC208C2ECA4797FXX /* LiveDrawWidget */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -317,8 +316,8 @@
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
||||
FDFABE1A1A3547B1B22DD2DD /* GoogleService-Info.plist in Resources */,
|
||||
CEF2F278ABBC5FBFD470A0F7 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
804CD03F6B584D08B7803C63 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
D60EEEF252214D45B67A0063 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -341,6 +340,8 @@
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)/.xcode.env",
|
||||
"$(SRCROOT)/.xcode.env.local",
|
||||
);
|
||||
name = "Bundle React Native code and images";
|
||||
outputFileListPaths = (
|
||||
@@ -373,7 +374,7 @@
|
||||
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;
|
||||
};
|
||||
563ACE2651AC39438F3B399B /* [CP-User] [RNFB] Crashlytics Configuration */ = {
|
||||
0EE1B67C9AAD8BD8562E800E /* [CP-User] [RNFB] Crashlytics Configuration */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@@ -387,6 +388,52 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n";
|
||||
};
|
||||
503A0FD3DBA862B4BE649BBB /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Sauci/Pods-Sauci-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem.framework/ExpoFileSystem",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoFont/ExpoFont.framework/ExpoFont",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoImage/ExpoImage.framework/ExpoImage",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoImage/SDWebImage.framework/SDWebImage",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoImage/SDWebImageAVIFCoder.framework/SDWebImageAVIFCoder",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoImage/SDWebImageSVGCoder.framework/SDWebImageSVGCoder",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoImage/SDWebImageWebPCoder.framework/SDWebImageWebPCoder",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoImage/libavif.framework/libavif",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoMediaLibrary/ExpoMediaLibrary.framework/ExpoMediaLibrary",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoModulesCore/ExpoModulesCore.framework/ExpoModulesCore",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoModulesJSI/ExpoModulesJSI.framework/ExpoModulesJSI",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoModulesWorklets/ExpoModulesWorklets.framework/ExpoModulesWorklets",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoFileSystem.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoFont.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoImage.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageAVIFCoder.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageSVGCoder.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageWebPCoder.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavif.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoMediaLibrary.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoModulesCore.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoModulesJSI.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoModulesWorklets.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Sauci/Pods-Sauci-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -398,9 +445,10 @@
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoMediaLibrary/ExpoMediaLibrary_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoNotifications/ExpoNotifications_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle",
|
||||
@@ -414,18 +462,16 @@
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PurchasesHybridCommon/PurchasesHybridCommon.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/ReachabilitySwift.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RevenueCat/RevenueCat.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-menu/EXDevMenu.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-view-shot/RNViewShotPrivacyInfo.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
@@ -433,9 +479,10 @@
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoMediaLibrary_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle",
|
||||
@@ -449,43 +496,54 @@
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PurchasesHybridCommon.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReachabilitySwift.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RevenueCat.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNViewShotPrivacyInfo.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Sauci/Pods-Sauci-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A1248897D24903117793599B /* [CP] Embed Pods Frameworks */ = {
|
||||
8490E10253F91A7BEB80F555 /* [CP-User] [RNFB] Core Configuration */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Sauci/Pods-Sauci-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
||||
"$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)",
|
||||
);
|
||||
name = "[CP-User] [RNFB] Core Configuration";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"note: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"note: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"note: -> RNFB build script started\"\necho \"note: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"note: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"note: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n if ! _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\"); then\n echo \"error: Failed to parse firebase.json, check for syntax errors.\"\n exit 1\n fi\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"error: python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACLine truncated
|
||||
};
|
||||
D58CF933D4804A0CB49C2891 /* [Expo Dev Launcher] Strip Local Network Keys for Release */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[Expo Dev Launcher] Strip Local Network Keys for Release";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Sauci/Pods-Sauci-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
shellScript = "# Strip dev-launcher-specific local network permission keys from non-Debug builds\n# This only removes _expo._tcp Bonjour services and the dev-launcher usage description.\n# Other Bonjour services and custom descriptions are preserved for production use.\n\nif [ \"$CONFIGURATION\" != \"Debug\" ]; then\n PLIST_PATH=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n if [ -f \"$PLIST_PATH\" ]; then\n # Check if NSBonjourServices exists\n if /usr/libexec/PlistBuddy -c \"Print :NSBonjourServices\" \"$PLIST_PATH\" >/dev/null 2>&1; then\n # Get the count of services\n COUNT=$(/usr/libexec/PlistBuddy -c \"Print :NSBonjourServices\" \"$PLIST_PATH\" 2>/dev/null | grep \"^ \" | wc -l | tr -d ' ')\n\n # Remove _expo._tcp\n for ((i=COUNT-1; i>=0; i--)); do\n SERVICE=$(/usr/libexec/PlistBuddy -c \"Print :NSBonjourServices:$i\" \"$PLIST_PATH\" 2>/dev/null || echo \"\")\n if echo \"$SERVICE\" | grep -q \"_expo._tcp\"; then\n /usr/libexec/PlistBuddy -c \"Delete :NSBonjourServices:$i\" \"$PLIST_PATH\" 2>/dev/null || true\n fi\n done\n\n # If the array is now empty, remove it entirely\n REMAINING=$(/usr/libexec/PlistBuddy -c \"Print :NSBonjourServices\" \"$PLIST_PATH\" 2>/dev/null | grep \"^ \" | wc -l | tr -d ' ')\n if [ \"$REMAINING\" -eq \"0\" ]; then\n /usr/libexec/PlistBuddy -c \"Delete :NSBonjourServices\" \"$PLIST_PATH\" 2>/dev/null || true\n fi\n fi\n\n # Only delete the description if it matches the dev-launcher default text\n DESC=$(/usr/libexec/PlistBuddy -c \"Print :NSLocalNetworkUsageDescription\" \"$PLIST_PATH\" 2>/dev/null || echo \"\")\n if echo \"$DESC\" | grep -q \"Expo Dev Launcher\"; then\n /usr/libexec/PlistBuddy -c \"Delete :NSLocalNetworkUsageDescription\" \"$PLIST_PATH\" 2>/dev/null || true\n fi\n fi\nfi\n";
|
||||
};
|
||||
DC3D179C6B62F16DA5F17C2F /* [Expo] Configure project */ = {
|
||||
E6F7433229E3C1448FC9F062 /* [Expo] Configure project */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -509,19 +567,6 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Sauci/expo-configure-project.sh\"\n";
|
||||
};
|
||||
FB4B019D835055733ACDE109 /* [CP-User] [RNFB] Core Configuration */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)",
|
||||
);
|
||||
name = "[CP-User] [RNFB] Core Configuration";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"note: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"note: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"note: -> RNFB build script started\"\necho \"note: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"note: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"note: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n if ! _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\"); then\n echo \"error: Failed to parse firebase.json, check for syntax errors.\"\n exit 1\n fi\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"error: python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACLine truncated
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -530,8 +575,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
|
||||
5254BDADE70741A0B2504FB1 /* noop-file.swift in Sources */,
|
||||
3F202C2A859975F48A83ADD7 /* ExpoModulesProvider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -545,17 +589,17 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
XX97289CEC7994AD5F4142XX /* PBXTargetDependency */ = {
|
||||
XX5D3A21868E45E6E5BDC3XX /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = XX540FB4ABC75CE91A6036XX /* LiveDrawWidget */;
|
||||
targetProxy = XXA7C7D5580082A497FD95XX /* PBXContainerItemProxy */;
|
||||
target = XXB3405CC208C2ECA4797FXX /* LiveDrawWidget */;
|
||||
targetProxy = XX77D941E1DB38490B8F4EXX /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-Sauci.debug.xcconfig */;
|
||||
baseConfigurationReference = FEACE2E5B4EA50AFBF4DDF34 /* Pods-Sauci.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -568,9 +612,13 @@
|
||||
"FB_SONARKIT_ENABLED=1",
|
||||
);
|
||||
INFOPLIST_FILE = Sauci/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.4;
|
||||
MARKETING_VERSION = 1.0.8;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -589,7 +637,7 @@
|
||||
};
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-Sauci.release.xcconfig */;
|
||||
baseConfigurationReference = A172EA788EF476A7083810FE /* Pods-Sauci.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -597,9 +645,13 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = BXVPBATDZY;
|
||||
INFOPLIST_FILE = Sauci/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.4;
|
||||
MARKETING_VERSION = 1.0.8;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -664,29 +716,39 @@
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios",
|
||||
"${PODS_ROOT}/ReactCommon",
|
||||
"${PODS_ROOT}/ReactCommon/react/nativemodule/core",
|
||||
"${PODS_ROOT}/React-runtimeexecutor",
|
||||
"${PODS_ROOT}/React-runtimeexecutor/platform/ios",
|
||||
"${PODS_ROOT}/ReactCommon-Samples",
|
||||
"${PODS_ROOT}/ReactCommon-Samples/platform/ios",
|
||||
"${PODS_ROOT}/React-Fabric/react/renderer/components/view/platform/cxx",
|
||||
"${PODS_ROOT}/React-NativeModulesApple",
|
||||
"${PODS_ROOT}/React-graphics",
|
||||
"${PODS_ROOT}/React-graphics/react/renderer/graphics/platform/ios",
|
||||
"${PODS_ROOT}/React-featureflags",
|
||||
"${PODS_ROOT}/React-renderercss",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(inherited)",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
USE_HERMES = true;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -733,33 +795,43 @@
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios",
|
||||
"${PODS_ROOT}/ReactCommon",
|
||||
"${PODS_ROOT}/ReactCommon/react/nativemodule/core",
|
||||
"${PODS_ROOT}/React-runtimeexecutor",
|
||||
"${PODS_ROOT}/React-runtimeexecutor/platform/ios",
|
||||
"${PODS_ROOT}/ReactCommon-Samples",
|
||||
"${PODS_ROOT}/ReactCommon-Samples/platform/ios",
|
||||
"${PODS_ROOT}/React-Fabric/react/renderer/components/view/platform/cxx",
|
||||
"${PODS_ROOT}/React-NativeModulesApple",
|
||||
"${PODS_ROOT}/React-graphics",
|
||||
"${PODS_ROOT}/React-graphics/react/renderer/graphics/platform/ios",
|
||||
"${PODS_ROOT}/React-featureflags",
|
||||
"${PODS_ROOT}/React-renderercss",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(inherited)",
|
||||
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||
USE_HERMES = true;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
XX1230635DC14DD95E28A2XX /* Release */ = {
|
||||
XX0A0FB078256184BF7EA2XX /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
@@ -772,7 +844,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ../targets/widget/generated.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
CURRENT_PROJECT_VERSION = 47;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = BXVPBATDZY;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -781,12 +853,17 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = LiveDrawWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.8;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.sauci.app.LiveDrawWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@@ -796,7 +873,7 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
XX710DB962925BCE8D59ACXX /* Debug */ = {
|
||||
XX42CD38636C47E26CBE73XX /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
@@ -808,7 +885,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ../targets/widget/generated.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
CURRENT_PROJECT_VERSION = 47;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = BXVPBATDZY;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -817,13 +894,18 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = LiveDrawWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.8;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.sauci.app.LiveDrawWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@@ -854,11 +936,11 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
XX63FAB1BD532DA0D8AE18XX /* Build configuration list for PBXNativeTarget "LiveDrawWidget" */ = {
|
||||
XX35D62F4FBF2C9086E8C9XX /* Build configuration list for PBXNativeTarget "LiveDrawWidget" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
XX710DB962925BCE8D59ACXX /* Debug */,
|
||||
XX1230635DC14DD95E28A2XX /* Release */,
|
||||
XX42CD38636C47E26CBE73XX /* Debug */,
|
||||
XX0A0FB078256184BF7EA2XX /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Expo
|
||||
internal import Expo
|
||||
import FirebaseCore
|
||||
import React
|
||||
import ReactAppDependencyProvider
|
||||
|
||||
@UIApplicationMain
|
||||
public class AppDelegate: ExpoAppDelegate {
|
||||
@main
|
||||
class AppDelegate: ExpoAppDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
|
||||
@@ -20,11 +20,12 @@ public class AppDelegate: ExpoAppDelegate {
|
||||
|
||||
reactNativeDelegate = delegate
|
||||
reactNativeFactory = factory
|
||||
bindReactNativeFactory(factory)
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
FirebaseApp.configure()
|
||||
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-10e8520570672fd76b2403b7e1e27f5198a6349a
|
||||
FirebaseApp.configure()
|
||||
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
|
||||
factory.startReactNative(
|
||||
withModuleName: "main",
|
||||
in: window,
|
||||
@@ -34,42 +35,39 @@ public class AppDelegate: ExpoAppDelegate {
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
// Linking API
|
||||
public override func application(
|
||||
_ app: UIApplication,
|
||||
open url: URL,
|
||||
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
||||
) -> Bool {
|
||||
super.application(app, open: url, options: options)
|
||||
|| RCTLinkingManager.application(app, open: url, options: options)
|
||||
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
|
||||
}
|
||||
|
||||
// Universal Links
|
||||
public override func application(
|
||||
_ application: UIApplication,
|
||||
continue userActivity: NSUserActivity,
|
||||
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
|
||||
) -> Bool {
|
||||
let result = RCTLinkingManager.application(
|
||||
application,
|
||||
continue: userActivity,
|
||||
restorationHandler: restorationHandler)
|
||||
return super.application(
|
||||
application,
|
||||
continue: userActivity,
|
||||
restorationHandler: restorationHandler) || result
|
||||
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
|
||||
}
|
||||
}
|
||||
|
||||
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
|
||||
// Extension point for config-plugins
|
||||
|
||||
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
||||
// needed to return the correct URL for expo-dev-client.
|
||||
bridge.bundleURL ?? bundleURL()
|
||||
}
|
||||
|
||||
override func bundleURL() -> URL? {
|
||||
#if DEBUG
|
||||
RCTBundleURLProvider.sharedSettings()
|
||||
.jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
|
||||
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
|
||||
#else
|
||||
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
||||
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 57 KiB |
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.7</string>
|
||||
<string>1.0.8</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -47,7 +47,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>46</string>
|
||||
<string>47</string>
|
||||
<key>GOOGLE_ANALYTICS_INAPURCHASE_COLLECTION_ENABLED</key>
|
||||
<false/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
@@ -103,13 +103,6 @@
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Automatic</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
<string>C56D.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
@@ -33,18 +41,171 @@
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeEmailAddress</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeName</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeSensitiveInfo</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeUserID</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypePhotosorVideos</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeAudioData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeOtherUserContent</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeCrashData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypePerformanceData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeOtherDiagnosticData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypePurchaseHistory</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<true/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,11 +1,12 @@
|
||||
<?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">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24053.1"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -17,15 +18,13 @@
|
||||
<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"/>
|
||||
<rect key="frame" x="146.5" y="376" width="100" height="100"/>
|
||||
</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"/>
|
||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="centerX" secondItem="EXPO-ContainerView" secondAttribute="centerX" id="cad2ab56f97c5429bf29decf850647a4216861d4"/>
|
||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="centerY" secondItem="EXPO-ContainerView" secondAttribute="centerY" id="1a145271b085b6ce89b1405a310f5b1bb7656595"/>
|
||||
</constraints>
|
||||
<color key="backgroundColor" name="SplashScreenBackground"/>
|
||||
</view>
|
||||
@@ -36,7 +35,10 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="SplashScreenLogo" width="414" height="736"/>
|
||||
<image name="SplashScreenLogo" width="100" height="100"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<namedColor name="SplashScreenBackground">
|
||||
<color alpha="1.000" blue="0.180392156862745" green="0.101960784313725" red="0.101960784313725" customColorSpace="sRGB" colorSpace="custom"/>
|
||||
</namedColor>
|
||||
|
||||
@@ -31,6 +31,8 @@ Ug==
|
||||
<key>alg</key>
|
||||
<string>rsa-v1_5-sha256</string>
|
||||
</dict>
|
||||
<key>EXUpdatesEnableBsdiffPatchSupport</key>
|
||||
<true/>
|
||||
<key>EXUpdatesEnabled</key>
|
||||
<true/>
|
||||
<key>EXUpdatesLaunchWaitMs</key>
|
||||
@@ -45,7 +47,7 @@ Ug==
|
||||
<string/>
|
||||
</dict>
|
||||
<key>EXUpdatesRuntimeVersion</key>
|
||||
<string>1.0.7</string>
|
||||
<string>1.0.8</string>
|
||||
<key>EXUpdatesURL</key>
|
||||
<string>https://ota.apps.jbrunton.co.uk/manifest</string>
|
||||
</dict>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
//
|
||||
// @generated
|
||||
// A blank Swift file must be created for native modules with Swift files to work correctly.
|
||||
//
|
||||
@@ -12,6 +12,12 @@ module.exports = {
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
// react-native-worklets ships a JS-only mock for its native module at
|
||||
// this path (react-native-reanimated depends on it internally and has
|
||||
// no equivalent of its own). Without this, requiring the real
|
||||
// react-native-worklets under Jest throws because no native module is
|
||||
// registered.
|
||||
'^react-native-worklets$': '<rootDir>/../../node_modules/react-native-worklets/src/mock.ts',
|
||||
},
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/__tests__/**/*.(spec|test).(ts|tsx)',
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
"release:android": "dotenv -e .env -- eas build --platform android --profile production --local",
|
||||
"release:android:preview": "dotenv -e .env -- eas build --platform android --profile preview --local",
|
||||
"release:android:upload": "bundle exec fastlane android upload",
|
||||
"release:android:ship": "bundle exec fastlane android release",
|
||||
"release:ios:upload": "bundle exec fastlane ios upload",
|
||||
"release:ios:ship": "bundle exec fastlane ios release",
|
||||
"release:preflight": "node scripts/release-preflight.mjs",
|
||||
"release:preflight:local-eas": "node scripts/release-preflight.mjs --local-eas",
|
||||
"ota:preflight:staging": "node scripts/ota-publish.mjs staging --preflight",
|
||||
"ota:preflight:production": "node scripts/ota-publish.mjs production --preflight",
|
||||
"ota:publish:staging": "node scripts/ota-publish.mjs staging",
|
||||
@@ -29,7 +34,7 @@
|
||||
"ota:doctor:production": "node scripts/ota-publish.mjs production --doctor"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bacons/apple-targets": "^0.1.6",
|
||||
"@bacons/apple-targets": "^5.0.0",
|
||||
"@bittingz/expo-widgets": "^3.0.2",
|
||||
"@expo/ngrok": "^4.1.0",
|
||||
"@expo/vector-icons": "^15.0.3",
|
||||
@@ -39,54 +44,55 @@
|
||||
"@react-native-firebase/app": "^23.8.2",
|
||||
"@react-native-firebase/crashlytics": "^23.8.4",
|
||||
"@react-native-google-signin/google-signin": "^16.1.1",
|
||||
"@react-navigation/bottom-tabs": "^7.0.0",
|
||||
"@react-navigation/native": "^7.0.0",
|
||||
"@react-navigation/native-stack": "^7.0.0",
|
||||
"@shopify/react-native-skia": "2.2.12",
|
||||
"@shopify/react-native-skia": "^2.6.9",
|
||||
"@supabase/supabase-js": "^2.39.0",
|
||||
"@tanstack/react-query": "^5.17.0",
|
||||
"babel-preset-expo": "~54.0.12",
|
||||
"babel-preset-expo": "~56.0.0",
|
||||
"base64-arraybuffer": "^1.0.2",
|
||||
"expo": "~54.0.0",
|
||||
"expo-apple-authentication": "~8.0.8",
|
||||
"expo-asset": "~12.0.13",
|
||||
"expo-av": "~16.0.8",
|
||||
"expo-blur": "~15.0.8",
|
||||
"expo-build-properties": "~1.0.10",
|
||||
"expo-clipboard": "~8.0.8",
|
||||
"expo-constants": "~18.0.14",
|
||||
"expo-dev-client": "~6.0.21",
|
||||
"expo-file-system": "~19.0.24",
|
||||
"expo-font": "~14.0.12",
|
||||
"expo-haptics": "~15.0.8",
|
||||
"expo-image": "~3.0.11",
|
||||
"expo-image-picker": "~17.0.11",
|
||||
"expo-linear-gradient": "~15.0.8",
|
||||
"expo-linking": "~8.0.12",
|
||||
"expo-local-authentication": "~17.0.9",
|
||||
"expo-media-library": "~18.2.1",
|
||||
"expo-notifications": "~0.32.17",
|
||||
"expo-router": "~6.0.24",
|
||||
"expo-secure-store": "~15.0.8",
|
||||
"expo-sharing": "~14.0.8",
|
||||
"expo-status-bar": "~3.0.9",
|
||||
"expo-updates": "~29.0.20",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"expo": "^56.0.0",
|
||||
"expo-apple-authentication": "~56.0.4",
|
||||
"expo-asset": "~56.0.24",
|
||||
"expo-audio": "~56.0.13",
|
||||
"expo-blur": "~56.0.4",
|
||||
"expo-build-properties": "~56.0.27",
|
||||
"expo-clipboard": "~56.0.4",
|
||||
"expo-constants": "~56.0.25",
|
||||
"expo-dev-client": "~56.0.26",
|
||||
"expo-file-system": "~56.0.11",
|
||||
"expo-font": "~56.0.7",
|
||||
"expo-haptics": "~56.0.3",
|
||||
"expo-image": "~56.0.12",
|
||||
"expo-image-picker": "~56.0.25",
|
||||
"expo-linear-gradient": "~56.0.4",
|
||||
"expo-linking": "~56.0.17",
|
||||
"expo-local-authentication": "~56.0.5",
|
||||
"expo-media-library": "~56.0.11",
|
||||
"expo-notifications": "~56.0.25",
|
||||
"expo-observe": "~56.0.29",
|
||||
"expo-router": "~56.2.20",
|
||||
"expo-secure-store": "~56.0.4",
|
||||
"expo-sharing": "~56.0.26",
|
||||
"expo-splash-screen": "~56.0.15",
|
||||
"expo-status-bar": "~56.0.4",
|
||||
"expo-system-ui": "~56.0.5",
|
||||
"expo-updates": "~56.0.26",
|
||||
"expo-video": "~56.1.4",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-android-widget": "^0.22.1",
|
||||
"react-native-compressor": "^1.13.0",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-gesture-handler": "~2.31.1",
|
||||
"react-native-purchases": "^9.6.12",
|
||||
"react-native-reanimated": "~4.1.1",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-reanimated": "4.3.1",
|
||||
"react-native-safe-area-context": "~5.7.0",
|
||||
"react-native-screens": "~4.26.0",
|
||||
"react-native-share": "^12.3.1",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-svg": "15.15.4",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-view-shot": "^4.0.3",
|
||||
"react-native-view-shot": "5.1.0",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.5.1",
|
||||
"react-native-worklets": "0.8.3",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -94,15 +100,16 @@
|
||||
"@testing-library/jest-native": "^5.4.3",
|
||||
"@testing-library/react-native": "^13.3.3",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/react": "~19.1.10",
|
||||
"@types/node": "^20.19.0",
|
||||
"@types/react": "~19.2.14",
|
||||
"@types/react-test-renderer": "^19.1.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"eoas": "^3.1.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-expo": "~10.0.0",
|
||||
"eslint-config-expo": "~56.0.4",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~54.0.18",
|
||||
"react-test-renderer": "19.1.0",
|
||||
"typescript": "~5.9.2"
|
||||
"jest-expo": "~56.0.5",
|
||||
"react-test-renderer": "19.2.3",
|
||||
"typescript": "~6.0.3"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const projectDir = resolve(import.meta.dirname, '..');
|
||||
@@ -140,4 +141,8 @@ export function main(args = process.argv.slice(2), environment = process.env) {
|
||||
run('npx', ['eoas', 'publish', '--branch', branch, ...parsePublishArgs(rawPublishArgs)], exportEnv);
|
||||
}
|
||||
|
||||
if (process.argv[1] === new URL(import.meta.url).pathname) main();
|
||||
// Compare real filesystem paths. A URL pathname percent-encodes spaces, so
|
||||
// comparing it to process.argv[1] silently skips main() under any checkout
|
||||
// whose absolute path contains a space, and every ota:* script then exits 0
|
||||
// having done nothing.
|
||||
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main();
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { existsSync, readFileSync, statSync, statfsSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { resolve, join } from 'node:path';
|
||||
|
||||
const projectDir = resolve(import.meta.dirname, '..');
|
||||
const repoRoot = resolve(projectDir, '../..');
|
||||
const certificatePath = 'apps/mobile/certs/certificate.pem';
|
||||
const semverPattern = /^\d+\.\d+\.\d+$/;
|
||||
const minimumFreeBuildBytes = 20 * 1024 ** 3;
|
||||
|
||||
function fail(message) {
|
||||
throw new Error(`Mobile release preflight: ${message}`);
|
||||
}
|
||||
|
||||
function plistValue(source, key, label) {
|
||||
const values = [...source.matchAll(new RegExp(`<key>${key}</key>\\s*<string>([^<]+)</string>`, 'g'))].map((match) => match[1]);
|
||||
if (values.length !== 1) fail(`expected one ${label} ${key}`);
|
||||
return values[0];
|
||||
}
|
||||
|
||||
function allMatches(source, expression, label) {
|
||||
const values = [...source.matchAll(expression)].map((match) => match[1].trim());
|
||||
if (!values.length) fail(`found no ${label}`);
|
||||
return values;
|
||||
}
|
||||
|
||||
function gitStatus(args, root = repoRoot) {
|
||||
const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' });
|
||||
if (result.error) fail(`could not run git ${args.join(' ')}: ${result.error.message}`);
|
||||
return result.status;
|
||||
}
|
||||
|
||||
export function validateReleaseSources({ root = repoRoot, isTracked } = {}) {
|
||||
const file = (relative) => readFileSync(join(root, relative), 'utf8');
|
||||
const app = JSON.parse(file('apps/mobile/app.json')).expo;
|
||||
const appConfig = file('apps/mobile/app.config.js');
|
||||
const androidGradle = file('apps/mobile/android/app/build.gradle');
|
||||
const androidStrings = file('apps/mobile/android/app/src/main/res/values/strings.xml');
|
||||
const xcode = file('apps/mobile/ios/Sauci.xcodeproj/project.pbxproj');
|
||||
const iosInfo = file('apps/mobile/ios/Sauci/Info.plist');
|
||||
const widgetInfo = file('apps/mobile/targets/widget/Info.plist');
|
||||
const iosExpo = file('apps/mobile/ios/Sauci/Supporting/Expo.plist');
|
||||
const publicVersion = app.version;
|
||||
const appConfigRuntime = allMatches(appConfig, /runtimeVersion:\s*['"]([^'"]+)['"]/g, 'explicit app.config.js runtimeVersion');
|
||||
const androidVersion = allMatches(androidGradle, /\bversionName\s+["']([^"']+)["']/g, 'Android versionName');
|
||||
const androidRuntime = allMatches(androidStrings, /<string name="expo_runtime_version">([^<]+)<\/string>/g, 'Android Expo runtime version');
|
||||
const xcodeMarketing = allMatches(xcode, /\bMARKETING_VERSION\s*=\s*([^;]+);/g, 'iOS MARKETING_VERSION settings');
|
||||
const widgetBuildSettings = [...xcode.matchAll(/CURRENT_PROJECT_VERSION\s*=\s*([^;]+);[\s\S]{0,900}?PRODUCT_BUNDLE_IDENTIFIER\s*=\s*com\.sauci\.app\.LiveDrawWidget;/g)].map((match) => match[1].trim());
|
||||
if (widgetBuildSettings.length !== 2) fail('expected Debug and Release LiveDrawWidget CURRENT_PROJECT_VERSION settings');
|
||||
|
||||
const versions = [
|
||||
publicVersion,
|
||||
...appConfigRuntime,
|
||||
...androidVersion,
|
||||
...androidRuntime,
|
||||
...xcodeMarketing,
|
||||
plistValue(iosInfo, 'CFBundleShortVersionString', 'iOS app Info.plist'),
|
||||
plistValue(widgetInfo, 'CFBundleShortVersionString', 'widget Info.plist'),
|
||||
plistValue(iosExpo, 'EXUpdatesRuntimeVersion', 'iOS Expo.plist'),
|
||||
];
|
||||
if (!versions.every((version) => semverPattern.test(version))) fail(`versions must be numeric SemVer: ${versions.join(', ')}`);
|
||||
if (new Set(versions).size !== 1) fail(`public or OTA runtime versions disagree: ${versions.join(', ')}`);
|
||||
|
||||
const appBuild = plistValue(iosInfo, 'CFBundleVersion', 'iOS app Info.plist');
|
||||
const widgetBuild = plistValue(widgetInfo, 'CFBundleVersion', 'widget Info.plist');
|
||||
if (!/^\d+$/.test(appBuild) || !/^\d+$/.test(widgetBuild)) fail('iOS build numbers must be positive integers');
|
||||
if (appBuild !== widgetBuild || widgetBuildSettings.some((build) => build !== appBuild)) {
|
||||
fail(`iOS app, widget, and effective widget Xcode build numbers disagree: app ${appBuild}; widget plist ${widgetBuild}; widget settings ${widgetBuildSettings.join(', ')}`);
|
||||
}
|
||||
|
||||
const tracked = isTracked ?? ((relative) => gitStatus(['ls-files', '--error-unmatch', relative], root) === 0);
|
||||
if (!tracked(certificatePath)) fail(`${certificatePath} must be tracked`);
|
||||
const archiveIgnore = existsSync(join(root, '.easignore')) ? '.easignore' : '.gitignore';
|
||||
if (!existsSync(join(root, archiveIgnore))) fail(`missing root ${archiveIgnore} archive rules`);
|
||||
const archiveRules = file(archiveIgnore).split(/\r?\n/).map((line) => line.trim());
|
||||
const pemRules = archiveRules.map((line, index) => line === '*.pem' ? index : -1).filter((index) => index >= 0);
|
||||
const certificateAllowlist = archiveRules.lastIndexOf(`!${certificatePath}`);
|
||||
if (!pemRules.length) fail(`${archiveIgnore} must retain the private-key-safe *.pem archive rule`);
|
||||
if (certificateAllowlist <= Math.max(...pemRules)) {
|
||||
fail(`${archiveIgnore} must later allowlist !${certificatePath} for the public OTA certificate`);
|
||||
}
|
||||
if (!file(certificatePath).trim()) fail(`${certificatePath} is empty`);
|
||||
|
||||
return { publicVersion, iosBuild: appBuild };
|
||||
}
|
||||
|
||||
export function validateLocalEas(environment = process.env) {
|
||||
const pluginPath = environment.EAS_LOCAL_BUILD_PLUGIN_PATH;
|
||||
if (!pluginPath) fail('EAS_LOCAL_BUILD_PLUGIN_PATH must name the matching local-build plugin executable bin/run');
|
||||
if (!pluginPath.endsWith('/bin/run') || !existsSync(pluginPath) || !statSync(pluginPath).isFile()) {
|
||||
fail('EAS_LOCAL_BUILD_PLUGIN_PATH must point to an existing executable bin/run, not a package directory');
|
||||
}
|
||||
if (!statSync(pluginPath).mode.toString(8).match(/[1357][0-7][0-7]$/)) fail('EAS_LOCAL_BUILD_PLUGIN_PATH bin/run is not executable');
|
||||
}
|
||||
|
||||
export function validateFreeDisk(root = repoRoot, readStats = statfsSync) {
|
||||
const stats = readStats(root);
|
||||
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
||||
if (freeBytes < minimumFreeBuildBytes) {
|
||||
fail(`need at least 20 GiB free before a local build; found ${(freeBytes / 1024 ** 3).toFixed(1)} GiB`);
|
||||
}
|
||||
}
|
||||
|
||||
export function main(args = process.argv.slice(2), environment = process.env) {
|
||||
if (args.length > 1 || (args.length === 1 && args[0] !== '--local-eas')) {
|
||||
fail('usage: node scripts/release-preflight.mjs [--local-eas]');
|
||||
}
|
||||
const result = validateReleaseSources();
|
||||
validateFreeDisk();
|
||||
if (args.includes('--local-eas')) validateLocalEas(environment);
|
||||
console.log(`Mobile release source preflight passed for v${result.publicVersion}, iOS build ${result.iosBuild}`);
|
||||
}
|
||||
|
||||
if (process.argv[1] === new URL(import.meta.url).pathname) main();
|
||||
@@ -0,0 +1,97 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { chmodSync, cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { validateFreeDisk, validateLocalEas, validateReleaseSources } from './release-preflight.mjs';
|
||||
|
||||
const repoRoot = join(import.meta.dirname, '..', '..', '..');
|
||||
|
||||
function fixture() {
|
||||
const root = mkdtempSync(join(tmpdir(), 'sauci-release-preflight-'));
|
||||
cpSync(join(repoRoot, 'apps', 'mobile'), join(root, 'apps', 'mobile'), { recursive: true });
|
||||
cpSync(join(repoRoot, '.gitignore'), join(root, '.gitignore'));
|
||||
return root;
|
||||
}
|
||||
|
||||
function replace(root, relative, from, to) {
|
||||
const file = join(root, relative);
|
||||
writeFileSync(file, readFileSync(file, 'utf8').replace(from, to));
|
||||
}
|
||||
|
||||
function validate(root) {
|
||||
return validateReleaseSources({ root, isTracked: () => true });
|
||||
}
|
||||
|
||||
test('accepts current checked-in mobile release sources', () => {
|
||||
const root = fixture();
|
||||
try {
|
||||
const result = validate(root);
|
||||
assert.match(result.publicVersion, /^\d+\.\d+\.\d+$/);
|
||||
assert.match(result.iosBuild, /^\d+$/);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects a widget Xcode build setting that overrides its plist', () => {
|
||||
const root = fixture();
|
||||
try {
|
||||
const { iosBuild } = validate(root);
|
||||
const project = 'apps/mobile/ios/Sauci.xcodeproj/project.pbxproj';
|
||||
const source = readFileSync(join(root, project), 'utf8');
|
||||
const expected = `CURRENT_PROJECT_VERSION = ${iosBuild};`;
|
||||
const replacement = `CURRENT_PROJECT_VERSION = ${Number(iosBuild) + 1};`;
|
||||
assert.ok(source.includes(expected));
|
||||
replace(root, project, expected, replacement);
|
||||
assert.ok(readFileSync(join(root, project), 'utf8').includes(replacement));
|
||||
assert.throws(() => validate(root), /iOS app, widget, and effective widget Xcode build numbers disagree/);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects an OTA certificate without its root archive allowlist', () => {
|
||||
const root = fixture();
|
||||
try {
|
||||
const archiveRules = readFileSync(join(root, '.gitignore'), 'utf8');
|
||||
assert.ok(archiveRules.includes('!apps/mobile/certs/certificate.pem'));
|
||||
replace(root, '.gitignore', '!apps/mobile/certs/certificate.pem', '');
|
||||
assert.ok(!readFileSync(join(root, '.gitignore'), 'utf8').includes('!apps/mobile/certs/certificate.pem'));
|
||||
assert.throws(
|
||||
() => validate(root),
|
||||
/must later allowlist !apps\/mobile\/certs\/certificate\.pem/,
|
||||
);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('requires the local EAS plugin executable rather than its package directory', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'sauci-eas-plugin-'));
|
||||
const executable = join(root, 'bin-run');
|
||||
try {
|
||||
writeFileSync(executable, '#!/bin/sh\n');
|
||||
chmodSync(executable, 0o755);
|
||||
assert.throws(
|
||||
() => validateLocalEas({ EAS_LOCAL_BUILD_PLUGIN_PATH: root }),
|
||||
/executable bin\/run, not a package directory/,
|
||||
);
|
||||
const bin = join(root, 'bin');
|
||||
mkdirSync(bin);
|
||||
const run = join(bin, 'run');
|
||||
cpSync(executable, run);
|
||||
chmodSync(run, 0o755);
|
||||
validateLocalEas({ EAS_LOCAL_BUILD_PLUGIN_PATH: run });
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('requires enough disk before an expensive local build', () => {
|
||||
assert.throws(
|
||||
() => validateFreeDisk('/fixture', () => ({ bavail: 19, bsize: 1024 ** 3 })),
|
||||
/need at least 20 GiB free/,
|
||||
);
|
||||
validateFreeDisk('/fixture', () => ({ bavail: 20, bsize: 1024 ** 3 }));
|
||||
});
|
||||
@@ -1,31 +1,43 @@
|
||||
import { act, renderHook } from '@testing-library/react-native';
|
||||
import { Alert } from 'react-native';
|
||||
import { Audio } from 'expo-av';
|
||||
import { AudioModule, requestRecordingPermissionsAsync, setAudioModeAsync } from 'expo-audio';
|
||||
|
||||
import { useAudioRecorder } from '@/hooks/useAudioRecorder';
|
||||
|
||||
// eslint-plugin-import's namespace rule cannot statically resolve the
|
||||
// `AudioRecorder` class exposed on the `AudioModule` native module instance.
|
||||
// Alias it once here so the mock constructor is only dereferenced this way
|
||||
// in a single, deliberately suppressed spot.
|
||||
// eslint-disable-next-line import/namespace
|
||||
const AudioRecorderMock = AudioModule.AudioRecorder as jest.Mock;
|
||||
|
||||
const makeRecording = (overrides: Record<string, unknown> = {}) => ({
|
||||
getStatusAsync: jest.fn(async () => ({ isRecording: true, durationMillis: 2400 })),
|
||||
stopAndUnloadAsync: jest.fn(async () => undefined),
|
||||
getURI: jest.fn(() => 'file://recording.m4a'),
|
||||
setOnRecordingStatusUpdate: jest.fn(),
|
||||
setProgressUpdateInterval: jest.fn(),
|
||||
getStatus: jest.fn(() => ({ isRecording: true, durationMillis: 2400, canRecord: true, url: null })),
|
||||
prepareToRecordAsync: jest.fn(async () => undefined),
|
||||
record: jest.fn(),
|
||||
stop: jest.fn(async () => undefined),
|
||||
uri: 'file://recording.m4a',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('useAudioRecorder', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(Alert, 'alert').mockImplementation(() => undefined);
|
||||
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
(Audio.requestPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' });
|
||||
(Audio.setAudioModeAsync as jest.Mock).mockResolvedValue(undefined);
|
||||
(requestRecordingPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' });
|
||||
(setAudioModeAsync as jest.Mock).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
afterEach(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
jest.useRealTimers();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('alerts and refuses to start without microphone permission', async () => {
|
||||
(Audio.requestPermissionsAsync as jest.Mock).mockResolvedValueOnce({ status: 'denied' });
|
||||
(requestRecordingPermissionsAsync as jest.Mock).mockResolvedValueOnce({ status: 'denied' });
|
||||
const { result } = renderHook(() => useAudioRecorder());
|
||||
|
||||
let started = true;
|
||||
@@ -36,21 +48,22 @@ describe('useAudioRecorder', () => {
|
||||
'Microphone Permission',
|
||||
'Please allow microphone access to record audio.'
|
||||
);
|
||||
expect(Audio.Recording.createAsync).not.toHaveBeenCalled();
|
||||
expect(AudioRecorderMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('starts, reports duration, and returns the completed recording', async () => {
|
||||
const recording = makeRecording();
|
||||
(Audio.Recording.createAsync as jest.Mock).mockResolvedValue({ recording });
|
||||
AudioRecorderMock.mockImplementation(() => recording);
|
||||
const onComplete = jest.fn();
|
||||
const { result } = renderHook(() => useAudioRecorder({ onRecordingComplete: onComplete }));
|
||||
|
||||
await act(async () => { expect(await result.current.startRecording()).toBe(true); });
|
||||
expect(result.current.state).toBe('recording');
|
||||
expect(recording.setProgressUpdateInterval).toHaveBeenCalledWith(100);
|
||||
expect(recording.prepareToRecordAsync).toHaveBeenCalled();
|
||||
expect(recording.record).toHaveBeenCalled();
|
||||
|
||||
const statusCallback = recording.setOnRecordingStatusUpdate.mock.calls[0][0];
|
||||
act(() => statusCallback({ isRecording: true, durationMillis: 2400 }));
|
||||
// Duration is reported by polling the recorder status, not by a push event.
|
||||
await act(async () => { jest.advanceTimersByTime(100); });
|
||||
expect(result.current.durationSeconds).toBe(2);
|
||||
|
||||
let completed: unknown;
|
||||
@@ -66,50 +79,55 @@ describe('useAudioRecorder', () => {
|
||||
|
||||
it('auto-stops when the configured duration is reached', async () => {
|
||||
const recording = makeRecording({
|
||||
getStatusAsync: jest.fn(async () => ({ isRecording: true, durationMillis: 1000 })),
|
||||
getStatus: jest.fn(() => ({ isRecording: true, durationMillis: 1000, canRecord: true, url: null })),
|
||||
});
|
||||
(Audio.Recording.createAsync as jest.Mock).mockResolvedValue({ recording });
|
||||
AudioRecorderMock.mockImplementation(() => recording);
|
||||
const onComplete = jest.fn();
|
||||
const { result } = renderHook(() => useAudioRecorder({ maxDurationSeconds: 1, onRecordingComplete: onComplete }));
|
||||
|
||||
await act(async () => { await result.current.startRecording(); });
|
||||
const statusCallback = recording.setOnRecordingStatusUpdate.mock.calls[0][0];
|
||||
await act(async () => { statusCallback({ isRecording: true, durationMillis: 1000 }); });
|
||||
await act(async () => { jest.advanceTimersByTime(100); });
|
||||
// Flush the microtasks queued by the auto-stop call.
|
||||
await act(async () => { await Promise.resolve(); });
|
||||
|
||||
expect(recording.stopAndUnloadAsync).toHaveBeenCalled();
|
||||
expect(recording.stop).toHaveBeenCalled();
|
||||
expect(onComplete).toHaveBeenCalledWith('file://recording.m4a', 1);
|
||||
});
|
||||
|
||||
it('resets an active recording and cleans it up on unmount', async () => {
|
||||
const first = makeRecording();
|
||||
const second = makeRecording();
|
||||
(Audio.Recording.createAsync as jest.Mock)
|
||||
.mockResolvedValueOnce({ recording: first })
|
||||
.mockResolvedValueOnce({ recording: second });
|
||||
AudioRecorderMock
|
||||
.mockImplementationOnce(() => first)
|
||||
.mockImplementationOnce(() => second);
|
||||
const view = renderHook(() => useAudioRecorder());
|
||||
|
||||
await act(async () => { await view.result.current.startRecording(); });
|
||||
act(() => view.result.current.resetRecording());
|
||||
expect(first.stopAndUnloadAsync).toHaveBeenCalled();
|
||||
expect(first.stop).toHaveBeenCalled();
|
||||
expect(view.result.current.state).toBe('idle');
|
||||
|
||||
await act(async () => { await view.result.current.startRecording(); });
|
||||
view.unmount();
|
||||
expect(second.stopAndUnloadAsync).toHaveBeenCalled();
|
||||
expect(Audio.setAudioModeAsync).toHaveBeenLastCalledWith({
|
||||
allowsRecordingIOS: false,
|
||||
playsInSilentModeIOS: true,
|
||||
expect(second.stop).toHaveBeenCalled();
|
||||
expect(setAudioModeAsync).toHaveBeenLastCalledWith({
|
||||
allowsRecording: false,
|
||||
playsInSilentMode: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('recovers when starting or stopping throws', async () => {
|
||||
(Audio.Recording.createAsync as jest.Mock).mockRejectedValueOnce(new Error('native unavailable'));
|
||||
AudioRecorderMock.mockImplementationOnce(() => {
|
||||
throw new Error('native unavailable');
|
||||
});
|
||||
const first = renderHook(() => useAudioRecorder());
|
||||
await act(async () => { expect(await first.result.current.startRecording()).toBe(false); });
|
||||
expect(first.result.current.state).toBe('idle');
|
||||
|
||||
const recording = makeRecording({ getStatusAsync: jest.fn(async () => { throw new Error('lost'); }) });
|
||||
(Audio.Recording.createAsync as jest.Mock).mockResolvedValueOnce({ recording });
|
||||
const recording = makeRecording({
|
||||
getStatus: jest.fn(() => { throw new Error('lost'); }),
|
||||
});
|
||||
AudioRecorderMock.mockImplementationOnce(() => recording);
|
||||
const second = renderHook(() => useAudioRecorder());
|
||||
await act(async () => { await second.result.current.startRecording(); });
|
||||
await act(async () => { expect(await second.result.current.stopRecording()).toBeNull(); });
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
jest.mock('expo-observe', () => ({
|
||||
Observe: {
|
||||
logEvent: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { Observe } = require('expo-observe');
|
||||
const logEventMock = Observe.logEvent as jest.Mock;
|
||||
|
||||
type ErrorHandler = (error: unknown, isFatal: boolean) => void;
|
||||
|
||||
function withFreshObserveModule<T>(run: (mod: typeof import('@/lib/observe')) => T): T {
|
||||
let result!: T;
|
||||
jest.isolateModules(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const mod = require('@/lib/observe') as typeof import('@/lib/observe');
|
||||
result = run(mod);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function installFakeErrorUtils(previousHandler: jest.Mock) {
|
||||
let installedHandler: ErrorHandler = () => undefined;
|
||||
const globalWithErrorUtils = global as unknown as {
|
||||
ErrorUtils: { getGlobalHandler: () => unknown; setGlobalHandler: (fn: unknown) => void };
|
||||
};
|
||||
globalWithErrorUtils.ErrorUtils = {
|
||||
getGlobalHandler: () => previousHandler,
|
||||
setGlobalHandler: (fn) => {
|
||||
installedHandler = fn as ErrorHandler;
|
||||
},
|
||||
};
|
||||
return () => installedHandler;
|
||||
}
|
||||
|
||||
describe('observe', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('logs app_started only once per module instance', () => {
|
||||
withFreshObserveModule(({ logAppStarted }) => {
|
||||
logAppStarted();
|
||||
logAppStarted();
|
||||
});
|
||||
|
||||
expect(logEventMock).toHaveBeenCalledTimes(1);
|
||||
expect(logEventMock).toHaveBeenCalledWith('app_started');
|
||||
});
|
||||
|
||||
it('does not throw when Observe.logEvent fails', () => {
|
||||
logEventMock.mockImplementationOnce(() => {
|
||||
throw new Error('native module unavailable');
|
||||
});
|
||||
|
||||
withFreshObserveModule(({ logAppStarted }) => {
|
||||
expect(() => logAppStarted()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps the previous ErrorUtils handler, logging xprem_js_crash and delegating', () => {
|
||||
const previousHandler = jest.fn();
|
||||
const getInstalledHandler = installFakeErrorUtils(previousHandler);
|
||||
|
||||
withFreshObserveModule(({ installObserveGlobalErrorHandler }) => {
|
||||
installObserveGlobalErrorHandler();
|
||||
});
|
||||
|
||||
const error = new Error('boom');
|
||||
getInstalledHandler()(error, true);
|
||||
|
||||
expect(logEventMock).toHaveBeenCalledWith(
|
||||
'xprem_js_crash',
|
||||
expect.objectContaining({
|
||||
severity: 'fatal',
|
||||
body: 'boom',
|
||||
})
|
||||
);
|
||||
expect(previousHandler).toHaveBeenCalledWith(error, true);
|
||||
});
|
||||
|
||||
it('does not log xprem_js_crash for non-fatal errors but still delegates', () => {
|
||||
const previousHandler = jest.fn();
|
||||
const getInstalledHandler = installFakeErrorUtils(previousHandler);
|
||||
|
||||
withFreshObserveModule(({ installObserveGlobalErrorHandler }) => {
|
||||
installObserveGlobalErrorHandler();
|
||||
});
|
||||
|
||||
const error = new Error('handled');
|
||||
getInstalledHandler()(error, false);
|
||||
|
||||
expect(logEventMock).not.toHaveBeenCalled();
|
||||
expect(previousHandler).toHaveBeenCalledWith(error, false);
|
||||
});
|
||||
|
||||
it('still calls the previous handler when Observe.logEvent throws', () => {
|
||||
const previousHandler = jest.fn();
|
||||
const getInstalledHandler = installFakeErrorUtils(previousHandler);
|
||||
logEventMock.mockImplementationOnce(() => {
|
||||
throw new Error('native module unavailable');
|
||||
});
|
||||
|
||||
withFreshObserveModule(({ installObserveGlobalErrorHandler }) => {
|
||||
installObserveGlobalErrorHandler();
|
||||
});
|
||||
|
||||
getInstalledHandler()(new Error('boom'), true);
|
||||
|
||||
expect(previousHandler).toHaveBeenCalledWith(expect.any(Error), true);
|
||||
});
|
||||
});
|
||||
@@ -310,7 +310,7 @@ const styles = StyleSheet.create({
|
||||
color: colors.textTertiary,
|
||||
},
|
||||
lockOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -307,7 +307,7 @@ export const MatchConfetti: React.FC<Props> = ({ visible, onAnimationComplete })
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
zIndex: 1000,
|
||||
},
|
||||
particle: {
|
||||
|
||||
@@ -344,7 +344,7 @@ export const MatchNotificationModal: React.FC<Props> = ({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
@@ -377,7 +377,7 @@ const styles = StyleSheet.create({
|
||||
borderColor: colors.border,
|
||||
},
|
||||
flatBackground: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: colors.backgroundLight,
|
||||
},
|
||||
// Removed blur/gradient styles
|
||||
|
||||
@@ -269,7 +269,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.85)",
|
||||
},
|
||||
content: {
|
||||
|
||||
@@ -101,7 +101,7 @@ const styles = StyleSheet.create({
|
||||
position: 'relative',
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.05)',
|
||||
borderRadius: radius.lg,
|
||||
pointerEvents: 'none',
|
||||
|
||||
@@ -115,7 +115,7 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '700',
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.05)',
|
||||
borderRadius: radius.lg,
|
||||
pointerEvents: 'none',
|
||||
|
||||
@@ -110,7 +110,7 @@ const styles = StyleSheet.create({
|
||||
marginTop: 2,
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.05)',
|
||||
borderRadius: radius.lg,
|
||||
pointerEvents: 'none',
|
||||
|
||||
@@ -90,7 +90,7 @@ const styles = StyleSheet.create({
|
||||
marginTop: 2,
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.05)',
|
||||
borderRadius: radius.lg,
|
||||
pointerEvents: 'none',
|
||||
|
||||
@@ -336,7 +336,7 @@ const styles = StyleSheet.create({
|
||||
zIndex: 1000,
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
},
|
||||
keyboardView: {
|
||||
|
||||
@@ -615,7 +615,7 @@ const styles = StyleSheet.create({
|
||||
borderColor: colors.premium.goldLight,
|
||||
},
|
||||
annualBackground: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: colors.premium.goldLight,
|
||||
opacity: 0.3,
|
||||
},
|
||||
@@ -804,7 +804,7 @@ const styles = StyleSheet.create({
|
||||
|
||||
// Guest confirm modal
|
||||
guestConfirmOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { View, Text, StyleSheet, Platform, TouchableOpacity } from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { Audio } from "expo-av";
|
||||
import { createAudioPlayer, type AudioPlayer } from "expo-audio";
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
@@ -59,7 +59,7 @@ export default function QuestionCardAudio({ question, packInfo, onAnswer, onRepo
|
||||
maxDurationSeconds: maxDuration,
|
||||
});
|
||||
|
||||
const [sound, setSound] = useState<Audio.Sound | null>(null);
|
||||
const [sound, setSound] = useState<AudioPlayer | null>(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [playbackPosition, setPlaybackPosition] = useState(0);
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function QuestionCardAudio({ question, packInfo, onAnswer, onRepo
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (sound) {
|
||||
sound.unloadAsync();
|
||||
sound.remove();
|
||||
}
|
||||
};
|
||||
}, [sound]);
|
||||
@@ -113,30 +113,26 @@ export default function QuestionCardAudio({ question, packInfo, onAnswer, onRepo
|
||||
await triggerHaptic('light');
|
||||
|
||||
if (isPlaying && sound) {
|
||||
await sound.pauseAsync();
|
||||
sound.pause();
|
||||
setIsPlaying(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sound) {
|
||||
await sound.playAsync();
|
||||
sound.play();
|
||||
setIsPlaying(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const { sound: newSound } = await Audio.Sound.createAsync(
|
||||
{ uri: recordingUri },
|
||||
{ shouldPlay: true },
|
||||
(status) => {
|
||||
if (status.isLoaded) {
|
||||
setPlaybackPosition(status.positionMillis / 1000);
|
||||
const newSound = createAudioPlayer({ uri: recordingUri });
|
||||
newSound.addListener('playbackStatusUpdate', (status) => {
|
||||
setPlaybackPosition(status.currentTime);
|
||||
if (status.didJustFinish) {
|
||||
setIsPlaying(false);
|
||||
setPlaybackPosition(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
newSound.play();
|
||||
setSound(newSound);
|
||||
setIsPlaying(true);
|
||||
};
|
||||
@@ -145,7 +141,7 @@ export default function QuestionCardAudio({ question, packInfo, onAnswer, onRepo
|
||||
if (!recordingUri) return;
|
||||
await triggerHaptic('medium');
|
||||
if (sound) {
|
||||
await sound.unloadAsync();
|
||||
sound.remove();
|
||||
setSound(null);
|
||||
}
|
||||
onAnswer('yes', { type: 'audio', media_path: recordingUri, duration_seconds: durationSeconds });
|
||||
@@ -154,7 +150,7 @@ export default function QuestionCardAudio({ question, packInfo, onAnswer, onRepo
|
||||
const handleReRecord = async () => {
|
||||
await triggerHaptic('light');
|
||||
if (sound) {
|
||||
await sound.unloadAsync();
|
||||
sound.remove();
|
||||
setSound(null);
|
||||
}
|
||||
setIsPlaying(false);
|
||||
@@ -165,7 +161,7 @@ export default function QuestionCardAudio({ question, packInfo, onAnswer, onRepo
|
||||
const handleCancel = async () => {
|
||||
await triggerHaptic('light');
|
||||
if (sound) {
|
||||
await sound.unloadAsync();
|
||||
sound.remove();
|
||||
setSound(null);
|
||||
}
|
||||
setIsPlaying(false);
|
||||
@@ -176,7 +172,7 @@ export default function QuestionCardAudio({ question, packInfo, onAnswer, onRepo
|
||||
const handleSkip = async () => {
|
||||
await triggerHaptic('light');
|
||||
if (sound) {
|
||||
await sound.unloadAsync();
|
||||
sound.remove();
|
||||
setSound(null);
|
||||
}
|
||||
onAnswer('skip');
|
||||
|
||||
@@ -131,7 +131,7 @@ const styles = StyleSheet.create({
|
||||
padding: spacing.lg,
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||
},
|
||||
modalContainer: {
|
||||
|
||||
@@ -629,7 +629,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
||||
},
|
||||
sheetContainer: {
|
||||
|
||||
@@ -86,7 +86,7 @@ const styles = StyleSheet.create({
|
||||
padding: spacing.lg,
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||||
},
|
||||
header: {
|
||||
|
||||
@@ -19,7 +19,7 @@ import Animated, {
|
||||
} from "react-native-reanimated";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import ViewShot from "react-native-view-shot";
|
||||
import ViewShot, { type ViewShotRef } from "react-native-view-shot";
|
||||
import * as Sharing from "expo-sharing";
|
||||
import { colors, typography, spacing, blur } from "../../theme";
|
||||
import { shareToInstagramStories } from "../../lib/instagramShare";
|
||||
@@ -50,7 +50,7 @@ export function SharePreviewModal({
|
||||
const insets = useSafeAreaInsets();
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
const cardWidth = Math.min(screenWidth - 64, MAX_CARD_WIDTH);
|
||||
const shareViewRef = useRef<ViewShot>(null);
|
||||
const shareViewRef = useRef<ViewShotRef>(null);
|
||||
const [capturedUri, setCapturedUri] = useState<string | null>(null);
|
||||
const [isCapturing, setIsCapturing] = useState(false);
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ export default function MatchesTutorial({ onComplete }: Props) {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
zIndex: 1000,
|
||||
},
|
||||
blur: {
|
||||
|
||||
@@ -152,7 +152,7 @@ const styles = StyleSheet.create({
|
||||
padding: spacing.md,
|
||||
},
|
||||
fullScreenContainer: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(13, 13, 26, 0.85)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -118,7 +118,7 @@ const styles = StyleSheet.create({
|
||||
overflow: 'hidden',
|
||||
},
|
||||
glowContainer: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
},
|
||||
edgeGlowTop: {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { View, StyleSheet, KeyboardAvoidingView, Platform, Alert } from "react-native";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useFocusEffect, useIsFocused } from "@react-navigation/native";
|
||||
import { useLocalSearchParams, useRouter, useFocusEffect, useIsFocused } from "expo-router";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import Animated, { FadeInDown } from "react-native-reanimated";
|
||||
|
||||
@@ -159,7 +159,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
||||
},
|
||||
sheetContainer: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { View, Text, TouchableOpacity, ActivityIndicator, StyleSheet } from 'react-native';
|
||||
import { Video, ResizeMode, AVPlaybackStatus } from 'expo-av';
|
||||
import { useVideoPlayer, VideoView } from 'expo-video';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { colors, gradients, spacing, radius } from '../../../theme';
|
||||
@@ -30,7 +30,6 @@ const ChatVideoPlayerComponent: React.FC<ChatVideoPlayerProps> = ({
|
||||
urlError,
|
||||
onFullScreen,
|
||||
}) => {
|
||||
const videoRef = useRef<Video>(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [hasEnded, setHasEnded] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -43,21 +42,38 @@ const ChatVideoPlayerComponent: React.FC<ChatVideoPlayerProps> = ({
|
||||
const isLocalFile = signedUrl?.startsWith('file://');
|
||||
const videoSource = isLocalFile ? signedUrl : (cachedUri || signedUrl);
|
||||
|
||||
// Handle playback status updates
|
||||
const handlePlaybackStatusUpdate = useCallback(async (status: AVPlaybackStatus) => {
|
||||
if (!status.isLoaded) {
|
||||
// Log error if load failed
|
||||
if ('error' in status && status.error) {
|
||||
console.error(`[ChatVideoPlayer] Video load error:`, status.error);
|
||||
setIsLoading(false);
|
||||
const player = useVideoPlayer(videoSource ? { uri: videoSource } : null, (setupPlayer) => {
|
||||
setupPlayer.loop = false;
|
||||
});
|
||||
|
||||
// Swap the player source when the cached local file becomes available,
|
||||
// without reloading on the initial mount (useVideoPlayer already loaded it).
|
||||
const mountedSourceRef = useRef(videoSource);
|
||||
useEffect(() => {
|
||||
if (videoSource && videoSource !== mountedSourceRef.current) {
|
||||
mountedSourceRef.current = videoSource;
|
||||
player.replace({ uri: videoSource });
|
||||
}
|
||||
}, [videoSource, player]);
|
||||
|
||||
// Handle playback status updates
|
||||
useEffect(() => {
|
||||
const statusSubscription = player.addListener('statusChange', ({ status, error }) => {
|
||||
if (status === 'error') {
|
||||
if (error) {
|
||||
console.error(`[ChatVideoPlayer] Video load error:`, error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
setIsLoading(status === 'loading');
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
setIsPlaying(status.isPlaying);
|
||||
const playingSubscription = player.addListener('playingChange', ({ isPlaying: playing }) => {
|
||||
setIsPlaying(playing);
|
||||
});
|
||||
|
||||
if (status.didJustFinish) {
|
||||
const endSubscription = player.addListener('playToEnd', () => {
|
||||
setHasEnded(true);
|
||||
setIsPlaying(false);
|
||||
|
||||
@@ -65,22 +81,26 @@ const ChatVideoPlayerComponent: React.FC<ChatVideoPlayerProps> = ({
|
||||
if (signedUrl && !cachedUri && !isLocalFile) {
|
||||
cacheVideoFile();
|
||||
}
|
||||
}
|
||||
}, [signedUrl, cachedUri, isLocalFile, cacheVideoFile]);
|
||||
});
|
||||
|
||||
return () => {
|
||||
statusSubscription.remove();
|
||||
playingSubscription.remove();
|
||||
endSubscription.remove();
|
||||
};
|
||||
}, [player, signedUrl, cachedUri, isLocalFile, cacheVideoFile]);
|
||||
|
||||
// Toggle play/pause with native-like behavior
|
||||
const handleTapToPlay = useCallback(async () => {
|
||||
if (!videoRef.current) return;
|
||||
|
||||
const handleTapToPlay = useCallback(() => {
|
||||
if (isPlaying) {
|
||||
await videoRef.current.pauseAsync();
|
||||
player.pause();
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
if (hasEnded) {
|
||||
await videoRef.current.setPositionAsync(0);
|
||||
player.replay();
|
||||
setHasEnded(false);
|
||||
}
|
||||
await videoRef.current.playAsync();
|
||||
player.play();
|
||||
setIsPlaying(true);
|
||||
|
||||
// Start caching when playback starts
|
||||
@@ -88,19 +108,19 @@ const ChatVideoPlayerComponent: React.FC<ChatVideoPlayerProps> = ({
|
||||
cacheVideoFile();
|
||||
}
|
||||
}
|
||||
}, [isPlaying, hasEnded, signedUrl, cachedUri, cacheVideoFile]);
|
||||
}, [isPlaying, hasEnded, player, signedUrl, cachedUri, cacheVideoFile]);
|
||||
|
||||
// Handle full screen
|
||||
const handleFullScreen = useCallback(() => {
|
||||
const uri = cachedUri || signedUrl;
|
||||
if (uri) {
|
||||
if (videoRef.current && isPlaying) {
|
||||
videoRef.current.pauseAsync();
|
||||
if (isPlaying) {
|
||||
player.pause();
|
||||
setIsPlaying(false);
|
||||
}
|
||||
onFullScreen(uri);
|
||||
}
|
||||
}, [cachedUri, signedUrl, isPlaying, onFullScreen]);
|
||||
}, [cachedUri, signedUrl, isPlaying, player, onFullScreen]);
|
||||
|
||||
if (urlError || !videoSource) {
|
||||
return (
|
||||
@@ -119,15 +139,11 @@ const ChatVideoPlayerComponent: React.FC<ChatVideoPlayerProps> = ({
|
||||
delayLongPress={300}
|
||||
style={styles.container}
|
||||
>
|
||||
<Video
|
||||
ref={videoRef}
|
||||
source={{ uri: videoSource }}
|
||||
<VideoView
|
||||
player={player}
|
||||
style={styles.video}
|
||||
resizeMode={ResizeMode.COVER}
|
||||
isLooping={false}
|
||||
shouldPlay={false}
|
||||
positionMillis={0}
|
||||
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
|
||||
contentFit="cover"
|
||||
nativeControls={false}
|
||||
/>
|
||||
|
||||
{/* Loading indicator */}
|
||||
@@ -203,13 +219,13 @@ const styles = StyleSheet.create({
|
||||
marginTop: spacing.xs,
|
||||
},
|
||||
loadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||
},
|
||||
playOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { View, StyleSheet, TouchableOpacity, Modal, ActivityIndicator, useWindowDimensions } from 'react-native';
|
||||
import { Video, ResizeMode } from 'expo-av';
|
||||
import { useVideoPlayer, VideoView } from 'expo-video';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
@@ -27,6 +27,30 @@ const FullScreenVideoModalComponent: React.FC<FullScreenVideoModalProps> = ({
|
||||
// Use safe area insets for button positioning to avoid notch/dynamic island
|
||||
const buttonTop = insets.top + 10;
|
||||
|
||||
const player = useVideoPlayer(uri ? { uri } : null, (setupPlayer) => {
|
||||
setupPlayer.loop = false;
|
||||
});
|
||||
|
||||
// Swap the source when a different video is shown, without reloading on
|
||||
// the initial mount (useVideoPlayer already loaded the first source).
|
||||
const mountedUriRef = useRef(uri);
|
||||
useEffect(() => {
|
||||
if (uri && uri !== mountedUriRef.current) {
|
||||
mountedUriRef.current = uri;
|
||||
player.replace({ uri });
|
||||
}
|
||||
}, [uri, player]);
|
||||
|
||||
// Play while the modal is visible, pause otherwise (there is no
|
||||
// declarative `shouldPlay` prop on `VideoView`).
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
player.play();
|
||||
} else {
|
||||
player.pause();
|
||||
}
|
||||
}, [visible, player]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
@@ -75,13 +99,11 @@ const FullScreenVideoModalComponent: React.FC<FullScreenVideoModalProps> = ({
|
||||
</TouchableOpacity>
|
||||
|
||||
{uri && (
|
||||
<Video
|
||||
source={{ uri }}
|
||||
<VideoView
|
||||
player={player}
|
||||
style={{ width: windowWidth, height: windowHeight * 0.8 }}
|
||||
resizeMode={ResizeMode.CONTAIN}
|
||||
useNativeControls
|
||||
shouldPlay={visible}
|
||||
isLooping={false}
|
||||
contentFit="contain"
|
||||
nativeControls
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -220,7 +220,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
// ...
|
||||
inputFieldBorder: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
borderRadius: radius.xl,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from 'rea
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { Image } from 'expo-image';
|
||||
import { Audio } from 'expo-av';
|
||||
import { createAudioPlayer, type AudioPlayer } from 'expo-audio';
|
||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||
|
||||
import { DecorativeSeparator } from '../../../components/ui';
|
||||
@@ -237,7 +237,7 @@ const AudioDisplay: React.FC<{
|
||||
}> = ({ responseSummary, userId, partnerName }) => {
|
||||
const [audioUrls, setAudioUrls] = useState<Record<string, string>>({});
|
||||
const [playingId, setPlayingId] = useState<string | null>(null);
|
||||
const [sounds, setSounds] = useState<Record<string, Audio.Sound>>({});
|
||||
const [sounds, setSounds] = useState<Record<string, AudioPlayer>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAudioUrls = async () => {
|
||||
@@ -266,7 +266,7 @@ const AudioDisplay: React.FC<{
|
||||
// Cleanup sounds on unmount
|
||||
return () => {
|
||||
Object.values(sounds).forEach(sound => {
|
||||
sound.unloadAsync();
|
||||
sound.remove();
|
||||
});
|
||||
};
|
||||
}, [responseSummary]);
|
||||
@@ -283,33 +283,31 @@ const AudioDisplay: React.FC<{
|
||||
|
||||
// If already playing this one, pause it
|
||||
if (playingId === responderId && sounds[responderId]) {
|
||||
await sounds[responderId].pauseAsync();
|
||||
sounds[responderId].pause();
|
||||
setPlayingId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any currently playing audio
|
||||
if (playingId && sounds[playingId]) {
|
||||
await sounds[playingId].stopAsync();
|
||||
sounds[playingId].pause();
|
||||
await sounds[playingId].seekTo(0);
|
||||
}
|
||||
|
||||
// Play the selected audio
|
||||
try {
|
||||
let sound = sounds[responderId];
|
||||
if (!sound) {
|
||||
const { sound: newSound } = await Audio.Sound.createAsync(
|
||||
{ uri: audioUrl },
|
||||
{ shouldPlay: false },
|
||||
(status) => {
|
||||
if (status.isLoaded && status.didJustFinish) {
|
||||
sound = createAudioPlayer({ uri: audioUrl });
|
||||
sound.addListener('playbackStatusUpdate', (status) => {
|
||||
if (status.didJustFinish) {
|
||||
setPlayingId(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
sound = newSound;
|
||||
});
|
||||
setSounds(prev => ({ ...prev, [responderId]: sound }));
|
||||
}
|
||||
await sound.playFromPositionAsync(0);
|
||||
await sound.seekTo(0);
|
||||
sound.play();
|
||||
setPlayingId(responderId);
|
||||
} catch (err) {
|
||||
console.error('Failed to play audio:', err);
|
||||
@@ -790,7 +788,7 @@ const styles = StyleSheet.create({
|
||||
height: '100%',
|
||||
},
|
||||
photoLoadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -179,7 +179,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
||||
},
|
||||
sheetContainer: {
|
||||
|
||||
@@ -125,7 +125,7 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
thumbnail: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 64,
|
||||
@@ -136,7 +136,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ActivityIndicator, Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useIsFocused } from "@react-navigation/native";
|
||||
import { useIsFocused } from "expo-router";
|
||||
import Animated, { FadeIn } from "react-native-reanimated";
|
||||
import { GradientBackground } from "../../components/ui";
|
||||
import { Paywall } from "../../components/paywall";
|
||||
|
||||
@@ -28,7 +28,7 @@ function imageMime(uri: string): string {
|
||||
return "image/jpeg";
|
||||
}
|
||||
|
||||
/** expo-av records .m4a on both platforms; web lands on webm. */
|
||||
/** expo-audio records .m4a on both platforms; web lands on webm. */
|
||||
function audioMime(uri: string): string {
|
||||
const ext = uri.split(".").pop()?.toLowerCase() ?? "m4a";
|
||||
if (ext === "webm") return "audio/webm";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ActivityIndicator, Image, Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Audio } from "expo-av";
|
||||
import { createAudioPlayer, type AudioPlayer } from "expo-audio";
|
||||
import { getMediaUrl } from "../../../lib/mediaApi";
|
||||
import { colors, featureColors, radius, spacing, typography } from "../../../theme";
|
||||
import type { DareProofType } from "../types";
|
||||
@@ -21,7 +21,7 @@ export interface DareProofViewProps {
|
||||
export function DareProofView({ proofMediaId, proofType }: DareProofViewProps) {
|
||||
const [url, setUrl] = useState<string | null>(null);
|
||||
const [failed, setFailed] = useState(false);
|
||||
const [sound, setSound] = useState<Audio.Sound | null>(null);
|
||||
const [sound, setSound] = useState<AudioPlayer | null>(null);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [loadingAudio, setLoadingAudio] = useState(false);
|
||||
|
||||
@@ -35,32 +35,30 @@ export function DareProofView({ proofMediaId, proofType }: DareProofViewProps) {
|
||||
}, [proofMediaId, proofType]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => { if (sound) void sound.unloadAsync(); };
|
||||
return () => { if (sound) sound.remove(); };
|
||||
}, [sound]);
|
||||
|
||||
const togglePlayback = useCallback(async () => {
|
||||
try {
|
||||
if (sound) {
|
||||
const status = await sound.getStatusAsync();
|
||||
if (status.isLoaded && status.isPlaying) {
|
||||
await sound.pauseAsync();
|
||||
if (sound.playing) {
|
||||
sound.pause();
|
||||
setPlaying(false);
|
||||
} else {
|
||||
await sound.replayAsync();
|
||||
await sound.seekTo(0);
|
||||
sound.play();
|
||||
setPlaying(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setLoadingAudio(true);
|
||||
const { url: signedUrl } = await getMediaUrl(proofMediaId);
|
||||
const created = await Audio.Sound.createAsync(
|
||||
{ uri: signedUrl },
|
||||
{ shouldPlay: true },
|
||||
(status) => {
|
||||
if (status.isLoaded && status.didJustFinish) setPlaying(false);
|
||||
},
|
||||
);
|
||||
setSound(created.sound);
|
||||
const created = createAudioPlayer({ uri: signedUrl });
|
||||
created.addListener('playbackStatusUpdate', (status) => {
|
||||
if (status.didJustFinish) setPlaying(false);
|
||||
});
|
||||
created.play();
|
||||
setSound(created);
|
||||
setPlaying(true);
|
||||
} catch {
|
||||
setFailed(true);
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
Alert,
|
||||
useWindowDimensions,
|
||||
} from 'react-native';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import { useLocalSearchParams, useRouter, useIsFocused } from 'expo-router';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
|
||||
@@ -21,7 +21,7 @@ import Animated, {
|
||||
} from 'react-native-reanimated';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import ViewShot from 'react-native-view-shot';
|
||||
import ViewShot, { type ViewShotRef } from 'react-native-view-shot';
|
||||
import * as Sharing from 'expo-sharing';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { colors, gradients, radius, typography, spacing, blur } from '../../../theme';
|
||||
@@ -44,7 +44,7 @@ export function DrawingShareModal({
|
||||
const insets = useSafeAreaInsets();
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
const cardWidth = Math.min(screenWidth - 64, MAX_CARD_WIDTH);
|
||||
const shareViewRef = useRef<ViewShot>(null);
|
||||
const shareViewRef = useRef<ViewShotRef>(null);
|
||||
const [capturedUri, setCapturedUri] = useState<string | null>(null);
|
||||
const [isCapturing, setIsCapturing] = useState(false);
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ const styles = StyleSheet.create({
|
||||
overflow: 'hidden',
|
||||
},
|
||||
proBorderGradient: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
opacity: 0.3,
|
||||
},
|
||||
proCardInner: {
|
||||
@@ -330,7 +330,7 @@ const styles = StyleSheet.create({
|
||||
overflow: 'hidden',
|
||||
},
|
||||
freeBorderGradient: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...StyleSheet.absoluteFill,
|
||||
},
|
||||
|
||||
freeHeader: {
|
||||
|
||||