Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
754fb08327 | ||
|
|
05fe7bf107 | ||
|
|
fa107895b6 | ||
|
|
d31b0273cf | ||
|
|
2f358c25e8 | ||
|
|
d2eb511e1d | ||
|
|
f62a941bc6 | ||
|
|
d784d14486 | ||
|
|
7b0d0225f9 | ||
|
|
c1d25701ca | ||
|
|
5359120dbd | ||
|
|
ed6b3a0ec0 | ||
|
|
efd3284272 | ||
|
|
46bc48726c | ||
|
|
ff47e492e4 | ||
|
|
be969274b9 | ||
|
|
359c1cfcb5 | ||
|
|
6c3fc5dff7 | ||
|
|
99d3927680 | ||
|
|
55482dae5f | ||
|
|
395599abfa |
No files matched your search
@@ -1 +0,0 @@
|
||||
/* @EvanBacon
|
||||
@@ -1,78 +0,0 @@
|
||||
name: 🐛 Expo Config Plugin Bug Report
|
||||
description: Report a reproducible bug with one of the Expo Config Plugins.
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
Thanks for taking the time to file a bug report! Please fill out this
|
||||
form as completely as possible.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
If you leave out sections there is a high likelihood your issue will be
|
||||
closed. If you have a question, not a bug report, please post it on our
|
||||
[forums](https://forums.expo.dev/) instead.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the issue in 1 or 2 sentences
|
||||
placeholder: >-
|
||||
Clearly describe what the expected behavior is vs. what is actually
|
||||
happening. This should be as short as possible, while still
|
||||
communicating all the necessary information. If your summary is just 'X
|
||||
library/method isn't working', then you need to continue debugging
|
||||
yourself and provide more information.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Config Plugin
|
||||
options:
|
||||
- '@config-plugins/apple-settings'
|
||||
- '@config-plugins/ios-stickers'
|
||||
- '@config-plugins/react-native-adjust'
|
||||
- '@config-plugins/react-native-blob-util'
|
||||
- '@config-plugins/react-native-branch'
|
||||
- '@config-plugins/react-native-callkeep'
|
||||
- '@config-plugins/react-native-dynamic-app-icon'
|
||||
- '@config-plugins/react-native-pdf'
|
||||
- '@config-plugins/react-native-siri-shortcut'
|
||||
- '@config-plugins/react-native-webrtc'
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What platform(s) does this occur on?
|
||||
multiple: true
|
||||
options:
|
||||
- Android
|
||||
- iOS
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: SDK Version
|
||||
description: What version of the Expo SDK are you using?
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproducible demo
|
||||
description: >-
|
||||
This should include as little code as possible, do not simply link your
|
||||
entire project. If a reproducible demo is not provided, it is very
|
||||
likely your issue will be closed. Read [here more
|
||||
guidance](https://stackoverflow.com/help/mcve).
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
Please make sure contributors can run your code and follow the steps
|
||||
your provided in order to reproduce the bug.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
**Realize that it is up to you to debug your code and be as certain as
|
||||
possible that the bug is with Expo, not with your own app.** [Here's an
|
||||
excellent guide to debugging you can
|
||||
follow](https://gist.github.com/brentvatne/5ac00cba0c70b7a06b89a56787d6bc4a).
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🤔 Question about an Expo Config Plugin
|
||||
about: >-
|
||||
If you have a question about an Expo Config Plugin, please ask it on the
|
||||
forums, discord, Twitter, or Stack Overflow.
|
||||
url: https://forums.expo.io/
|
||||
- about: Adjust React Native SDK
|
||||
name: 📦 react-native-adjust issues
|
||||
url: https://github.com/adjust/react_native_sdk/issues
|
||||
- about: >-
|
||||
A module provides upload, download, and files access API. Supports file
|
||||
stream read/write for process large files.
|
||||
name: 📦 react-native-blob-util issues
|
||||
url: https://github.com/RonRadtke/react-native-blob-util/issues
|
||||
- about: Branch Metrics React Native SDK
|
||||
name: 📦 react-native-branch issues
|
||||
url: >-
|
||||
https://github.com/BranchMetrics/react-native-branch-deep-linking-attribution/issues
|
||||
- about: iOS 10 CallKit and Android ConnectionService Framework For React Native
|
||||
name: 📦 react-native-callkeep issues
|
||||
url: https://github.com/react-native-webrtc/react-native-callkeep/issues
|
||||
- about: Programmatically change the app icon in React Native.
|
||||
name: 📦 react-native-dynamic-app-icon issues
|
||||
url: https://github.com/idearockers/react-native-dynamic-app-icon/issues
|
||||
- about: A react native PDF view component, support ios and android platform
|
||||
name: 📦 react-native-pdf issues
|
||||
url: https://github.com/wonday/react-native-pdf/issues
|
||||
- about: A React Native library that enables you to use iOS 12+ Siri Shortcuts.
|
||||
name: 📦 react-native-siri-shortcut issues
|
||||
url: https://github.com/Gustash/react-native-siri-shortcut/issues
|
||||
- about: WebRTC for React Native
|
||||
name: 📦 react-native-webrtc issues
|
||||
url: https://github.com/react-native-webrtc/react-native-webrtc/issues
|
||||
@@ -1,22 +0,0 @@
|
||||
name: '⭐️ Config Plugin Request'
|
||||
description: 'Suggest an idea for a new Expo Config Plugin.'
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "This repo is used for publishing out-of-tree plugins that should eventually be upstreamed in a particular package. If the plugin is a utility or used to improve some particular workflow i.e. updates version numbers, than it's probably better suited for a personal repo or utility package."
|
||||
- type: input
|
||||
attributes:
|
||||
label: Library
|
||||
description: What package does this Config Plugin configure.
|
||||
placeholder: URL to a React Native package.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Add some info on why this integration is important.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Any existing examples?
|
||||
placeholder: Have you already created a local plugin for the requested library?
|
||||
@@ -1,24 +0,0 @@
|
||||
<!--
|
||||
🚨 We use semantic release (a bot publishes everything automatically)
|
||||
🚨 so please use conventional commit style for the PR title:
|
||||
🚨 https://www.conventionalcommits.org/en/v1.0.0/
|
||||
🚨 Example: feat(ios-stickers): add new step
|
||||
-->
|
||||
|
||||
# Why
|
||||
|
||||
<!--
|
||||
Please describe the motivation for this PR, and link to relevant GitHub issues, forums posts, or feature requests.
|
||||
-->
|
||||
|
||||
# How
|
||||
|
||||
<!--
|
||||
How did you build this feature or fix this bug and why?
|
||||
-->
|
||||
|
||||
# Test Plan
|
||||
|
||||
<!--
|
||||
Please describe how you tested this change and how a reviewer could reproduce your test, especially if this PR does not include automated tests! If possible, please also provide terminal output and/or screenshots demonstrating your test/reproduction.
|
||||
-->
|
||||
@@ -0,0 +1,155 @@
|
||||
name: E2E Test
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- .github/workflows/ios.yml
|
||||
- apps/app/**
|
||||
- packages/**
|
||||
- yarn.lock
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/ios.yml
|
||||
- apps/app/**
|
||||
- packages/**
|
||||
- yarn.lock
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ios:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: 👀 Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: 🍺 Install required tools
|
||||
run: |
|
||||
brew tap wix/brew
|
||||
brew install applesimutils
|
||||
brew install watchman
|
||||
echo "$(pwd)/bin" >> $GITHUB_PATH
|
||||
- name: 💎 Setup Ruby and install gems
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
- name: ♻️ Restore workspace node modules
|
||||
uses: actions/cache@v2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: |
|
||||
# See "workspaces" → "packages" in the root package.json for the source of truth of
|
||||
# which node_modules are affected by the root yarn.lock
|
||||
node_modules
|
||||
apps/*/node_modules
|
||||
packages/*/node_modules
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- name: 🧶 Install node modules in root dir
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: ♻️ Restore apps/app/ios/Pods from cache
|
||||
uses: actions/cache@v2
|
||||
id: pods-cache
|
||||
with:
|
||||
path: "apps/app/ios/Pods"
|
||||
key: ${{ runner.os }}-app-pods-${{ hashFiles('apps/app/ios/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-app-pods-
|
||||
- run: yarn global add expo-cli
|
||||
- name: 🔨 Prebuild
|
||||
run: expo prebuild -p ios
|
||||
working-directory: apps/app
|
||||
- name: 🕵️ Debug CocoaPods lockfiles
|
||||
run: git diff Podfile.lock Pods/Manifest.lock
|
||||
working-directory: apps/app/ios
|
||||
continue-on-error: true
|
||||
- name: ⚛️ Display React Native config
|
||||
run: yarn react-native config
|
||||
working-directory: apps/app
|
||||
- name: 🌳 Display pod environment
|
||||
run: pod env
|
||||
working-directory: apps/app/ios
|
||||
- name: 🥥 Install pods in apps/app/ios
|
||||
run: pod install
|
||||
if: steps.pods-cache.outputs.cache-hit != 'true' || hashFiles('apps/app/ios/Podfile.lock') != hashFiles('apps/app/ios/Pods/Manifest.lock')
|
||||
working-directory: apps/app/ios
|
||||
- name: Clean Detox
|
||||
run: yarn detox:clean
|
||||
working-directory: apps/app
|
||||
- name: Build iOS project for Detox
|
||||
run: yarn build:ios-release
|
||||
working-directory: apps/app
|
||||
timeout-minutes: 30
|
||||
- name: Run tests
|
||||
run: yarn test:ios-release
|
||||
working-directory: apps/app
|
||||
- name: Store images of build failures
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-artifacts
|
||||
path: apps/app/artifacts
|
||||
|
||||
android:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: 👀 Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: 🍺 Install required tools
|
||||
run: |
|
||||
brew install watchman
|
||||
echo "$(pwd)/bin" >> $GITHUB_PATH
|
||||
- name: ♻️ Restore workspace node modules
|
||||
uses: actions/cache@v2
|
||||
id: node-modules-cache
|
||||
with:
|
||||
path: |
|
||||
# See "workspaces" → "packages" in the root package.json for the source of truth of
|
||||
# which node_modules are affected by the root yarn.lock
|
||||
node_modules
|
||||
apps/*/node_modules
|
||||
packages/*/node_modules
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- name: 🧶 Install node modules in root dir
|
||||
run: yarn install --frozen-lockfile
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-v20210625-${{ hashFiles('android/*.gradle*') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-v20210625
|
||||
- run: yarn global add expo-cli
|
||||
- name: 🔨 Prebuild
|
||||
run: expo prebuild -p android
|
||||
working-directory: apps/app
|
||||
- name: ⚛️ Display React Native config
|
||||
run: yarn react-native config
|
||||
working-directory: apps/app
|
||||
- name: Clean Detox
|
||||
run: yarn detox:clean
|
||||
working-directory: apps/app
|
||||
- name: Build Android project for Detox
|
||||
run: yarn build:android-release
|
||||
working-directory: apps/app
|
||||
timeout-minutes: 35
|
||||
env:
|
||||
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=180000 -Dorg.gradle.internal.http.socketTimeout=180000 -Dorg.gradle.internal.network.retry.max.attempts=18 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
||||
- name: Run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 29
|
||||
avd-name: app
|
||||
script: yarn test:android-release
|
||||
working-directory: ./apps/app
|
||||
- name: Store images of build failures
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-artifacts
|
||||
path: apps/app/artifacts
|
||||
@@ -2,37 +2,19 @@ name: Check Markdown links
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [master]
|
||||
paths:
|
||||
- .github/workflows/lint-md.yml
|
||||
- '**.md'
|
||||
- "**.md"
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/lint-md.yml
|
||||
- '**.md'
|
||||
- "**.md"
|
||||
|
||||
jobs:
|
||||
lint-md:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 👀 Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: 🔨 Install pnpm
|
||||
uses: pnpm/setup@703c52620218391530e48b9e8870d5c0082e1b9b # v2.1.0
|
||||
with:
|
||||
install: false
|
||||
|
||||
- name: 🔨 Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install packages
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Check Markdown links
|
||||
run: pnpm exec remark --use validate-links . .github
|
||||
- uses: actions/checkout@v2
|
||||
- run: npm install
|
||||
- run: npx remark --use validate-links . .github
|
||||
@@ -1,48 +1,60 @@
|
||||
name: Test Packages
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request: {}
|
||||
branches: [master]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["16", "17"]
|
||||
name: Build with Node ${{ matrix.node }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn lerna run prepare --stream
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: "*"
|
||||
key: v2-${{ github.sha }}-${{ matrix.node }}
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: ['22', '24']
|
||||
name: Build and test on Node ${{ matrix.node }}
|
||||
node: ["16", "17"]
|
||||
package: [
|
||||
# Manually add packages here
|
||||
detox,
|
||||
ffmpeg-kit-react-native,
|
||||
react-native-ble-plx,
|
||||
react-native-blob-util,
|
||||
react-native-google-cast,
|
||||
react-native-pdf,
|
||||
# react-native-quick-actions,
|
||||
]
|
||||
name: Test ${{ matrix.package }} on Node ${{ matrix.node }}
|
||||
steps:
|
||||
- name: 👀 Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: 🔨 Install pnpm
|
||||
uses: pnpm/setup@703c52620218391530e48b9e8870d5c0082e1b9b # v2.1.0
|
||||
with:
|
||||
install: false
|
||||
|
||||
- name: 🔨 Setup Node
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
path: "*"
|
||||
key: v2-${{ github.sha }}-${{ matrix.node }}
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install packages
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build all packages
|
||||
run: pnpm --filter './packages/*' run build
|
||||
|
||||
- name: Lint all packages
|
||||
run: pnpm --filter './packages/*' run lint --deny-warnings
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm format --check
|
||||
|
||||
- name: Test all packages
|
||||
run: pnpm --filter './packages/*' run test --passWithNoTests
|
||||
- name: Test ${{ matrix.package }}
|
||||
run: cd packages/${{ matrix.package }} && yarn test
|
||||
env:
|
||||
CI: true
|
||||
EXPO_DEBUG: true
|
||||
@@ -9,7 +9,7 @@ web-build
|
||||
# Node
|
||||
node_modules
|
||||
npm-debug.log
|
||||
pnpm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Ruby
|
||||
.direnv
|
||||
@@ -48,22 +48,7 @@ xcuserdata
|
||||
.project
|
||||
.settings
|
||||
|
||||
# WebStorm
|
||||
.idea
|
||||
|
||||
# VSCode
|
||||
.history/
|
||||
|
||||
# Ignore build folders
|
||||
|
||||
packages/*/build/**
|
||||
packages/*/plugin/build/**
|
||||
packages/@*/*/build/**
|
||||
|
||||
|
||||
# App native folders
|
||||
|
||||
apps/*/ios/*
|
||||
apps/*/android/*
|
||||
apps/*/web-build/*
|
||||
apps/*/dist/*
|
||||
# packages/*/build/
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"bracketSameLine": true,
|
||||
"trailingComma": "es5",
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/**",
|
||||
"**/build/**",
|
||||
"**/dist/**",
|
||||
"**/__generated__/**",
|
||||
"**/fixtures/**",
|
||||
"**/.expo/**",
|
||||
"**/assets/**",
|
||||
"**/android/**",
|
||||
"**/ios/**",
|
||||
"**/expo-env.d.ts",
|
||||
"packages/apple-settings/src/schema/SettingsPlist.ts"
|
||||
],
|
||||
"sortImports": {
|
||||
"groups": [
|
||||
["builtin", "external"],
|
||||
["internal", "subpath"],
|
||||
["parent", "sibling", "index"],
|
||||
"unknown"
|
||||
],
|
||||
"ignoreCase": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
2.7.0
|
||||
@@ -1,107 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
Monorepo of community-maintained Expo config plugins for React Native libraries. Plugins auto-configure native Android/iOS projects during `npx expo prebuild`.
|
||||
|
||||
- **Namespace**: `@config-plugins/*`
|
||||
- **Package manager**: pnpm 12 with workspaces (version pinned in `devEngines.packageManager` in the root `package.json`)
|
||||
- **Publishing**: `pnpm publish` (independent versioning)
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Build all packages
|
||||
pnpm prepare
|
||||
|
||||
# Watch build all packages
|
||||
pnpm start
|
||||
|
||||
# Generate a new plugin
|
||||
pnpm gen
|
||||
|
||||
# Run tests for a specific package
|
||||
cd packages/<name> && pnpm test
|
||||
|
||||
# Lint a specific package
|
||||
cd packages/<name> && pnpm lint --deny-warnings
|
||||
|
||||
# Format the whole repo (add --check to only verify)
|
||||
pnpm format
|
||||
|
||||
# Build a specific package
|
||||
cd packages/<name> && pnpm build
|
||||
|
||||
# Clean a specific package
|
||||
cd packages/<name> && pnpm clean
|
||||
|
||||
# Rebuild an example app to test config plugins
|
||||
cd apps/<name> && pnpm expo prebuild --clean
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `packages/` — Config plugin packages (each has `src/`, `__tests__/`)
|
||||
- `apps/` — Example apps for testing plugins
|
||||
- `fixtures/` — Shared test fixtures (native config files: AppDelegate, build.gradle, Podfile, etc.)
|
||||
- `scripts/` — Repo tooling (plugin generator, dependabot config, issue templates)
|
||||
|
||||
## Conventions
|
||||
|
||||
- Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) (e.g., `feat(react-native-foo): add bar`)
|
||||
- Branch naming: `@your-github-username/type/description`
|
||||
- Each package runs `tsc`, `jest` and `oxlint` directly. Shared config lives at the root: `tsconfig.base.json`, `jest.config.base.js`, `oxlint.config.mjs`. Formatting is `oxfmt` with `.oxfmtrc.json`
|
||||
- TypeScript source in `src/`, compiled output in `build/`
|
||||
- Shared dependency versions are defined in the `catalog:` block in `pnpm-workspace.yaml` and referenced with `catalog:` in each package's `package.json`
|
||||
- Peer dependency on `expo@^56`
|
||||
- Tests use Jest with `memfs` for virtual filesystem testing
|
||||
|
||||
## Upgrading Plugins for a New Expo SDK Version
|
||||
|
||||
When a new Expo SDK is released (e.g., SDK 54 → SDK 55), every package and example app must be updated. Use conventional commits scoped to the package name (e.g., `chore(react-native-foo): bump to expo@^55`).
|
||||
|
||||
### Catalog updates (`pnpm-workspace.yaml`)
|
||||
|
||||
Update the `catalog:` block with the new SDK versions for shared dependencies:
|
||||
|
||||
1. **`expo`** — Update to `^<NEW_SDK>` (used as `peerDependencies` in all packages).
|
||||
2. **`@expo/config-plugins`** — Update to `^<NEW_SDK>`.
|
||||
3. Review other catalog entries (`@expo/image-utils`, `@expo/plist`, etc.) and bump if needed.
|
||||
|
||||
### Per-package updates (`packages/<name>/`)
|
||||
|
||||
For each package:
|
||||
|
||||
1. **`README.md`** — Add a new row to the version compatibility table with the new SDK version, the latest compatible upstream package version, and the next config plugin major version.
|
||||
2. **Build & test** — Run `pnpm build` and `pnpm test` (if tests exist) to verify nothing broke.
|
||||
|
||||
### Example app updates (`apps/<name>/`)
|
||||
|
||||
For each example app:
|
||||
|
||||
1. **`package.json`** — Update `expo` to the new SDK version (e.g., `~54` → `~55`).
|
||||
2. Run `npx expo install --fix` to align all other dependencies (`react`, `react-native`, `expo-splash-screen`, etc.) to compatible versions.
|
||||
3. Remove any stale vendored templates or files (e.g., old `expo-template-bare-minimum-*.tgz`).
|
||||
|
||||
### Monorepo root updates
|
||||
|
||||
1. **`scripts/generate-plugin.ts`** — Update `SDK_VERSION` to the new SDK number.
|
||||
|
||||
### Final steps
|
||||
|
||||
1. Run `pnpm install` from the root to regenerate `pnpm-lock.yaml`.
|
||||
2. Run `pnpm prepare` to verify all packages build.
|
||||
3. Run tests for packages that have them.
|
||||
4. Update `CLAUDE.md` if any conventions changed (e.g., the peer dependency version listed under Conventions).
|
||||
|
||||
## After Adding/Removing Packages
|
||||
|
||||
Run these from the root:
|
||||
|
||||
```bash
|
||||
pnpm update-dependabot-config
|
||||
pnpm update-issue-template
|
||||
```
|
||||
@@ -1,40 +0,0 @@
|
||||
# Contributing to Expo Config Plugins
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
To submit a pull request:
|
||||
|
||||
1. Fork the [repository](https://github.com/expo/config-plugins) and create a feature branch. (Existing contributors can create feature branches without forking. Prefix the branch name with `@your-github-username/`.)
|
||||
2. Run `pnpm install` in the root directory to generate the build files.
|
||||
3. Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) including in the PR title.
|
||||
4. Fill out the entire PR template. Ensure your code is tested locally and has continuous tests in CI.
|
||||
5. Make sure all tests pass on GitHub Actions.
|
||||
6. Wait for a review and adjust the code if necessary.
|
||||
|
||||
## Creating a plugin
|
||||
|
||||
- Plugins can be generated by running `pnpm gen`.
|
||||
- Add plugins to the example in [`apps/app`](/apps/app/package.json) to test them.
|
||||
- The example app should always stay in a state of regeneration.
|
||||
|
||||
Be sure to run the following scripts in the **root directory** to update automation tools:
|
||||
|
||||
- Update dependabot: `pnpm update-dependabot-config`
|
||||
- Update the issue template: `pnpm update-issue-template`
|
||||
|
||||
## Upgrading plugins for a new Expo version
|
||||
|
||||
- Update the `catalog:` block in `pnpm-workspace.yaml` with the new SDK versions for `expo`, `@expo/config-plugins`, and any other shared dependencies
|
||||
- Ensure every project under `apps/` is updated to latest version of Expo
|
||||
- Update each library’s `README.md`
|
||||
- Ensuring the latest version of Expo is listed
|
||||
- Ensure the upstream package version is correct
|
||||
- Use the next version for the matching config plugin
|
||||
- Update `SDK_VERSION` in [`scripts/generate-plugin.ts`](https://github.com/expo/config-plugins/blob/566f54e785147d88cb4c98490c3e536ee7ef3001/scripts/generate-plugin.ts#L67)
|
||||
|
||||
## Publishing a release
|
||||
|
||||
Packages are published manually using the scripts in the root `package.json`:
|
||||
|
||||
1. `pnpm bump`: Increments the major version of all packages.
|
||||
2. `pnpm release`: Publishes all packages to npm.
|
||||
@@ -1,17 +1,10 @@
|
||||
# Expo Config Plugins
|
||||
|
||||
This repository is a collection of assorted [Expo config plugins](https://docs.expo.dev/guides/config-plugins/) for configuring the `npx expo prebuild` command. It is meant to be a temporary home for packages that don't have a built-in Config Plugin.
|
||||
A collection of custom WIP config plugins for Expo dev clients. Use at your own discretion.
|
||||
|
||||
- **We prefer packages to ship their own Expo config plugin**. This helps ensure that plugin and package versions are always aligned, and that new features are made available in both the package and plugin simultaneously. If packages haven't adopted config plugins yet, the community can add a package here as a temporary home.
|
||||
|
||||
- **All packages here are community maintained.** They are not maintained by Expo. Feel free to improve docs, packages, tests, etc.
|
||||
## Creating a plugin
|
||||
|
||||
- **We maintain a one-to-one mapping between native packages and `@config-plugins/*`**. This means there should be no general utility packages here — those are better suited for a different repository or npm namespace.
|
||||
|
||||
## Looking for other config plugins?
|
||||
|
||||
Many React Native libraries ship their own built-in config plugins. You can find them on the [React Native Directory](https://reactnative.directory/).
|
||||
|
||||
## Contributing!
|
||||
|
||||
See the [contributing guide](/CONTRIBUTING.md).
|
||||
- Plugins can be generated by running `yarn gen`.
|
||||
- Add plugins to the example in [`apps/app`](/apps/app/package.json) to test them.
|
||||
- The example app should always stay in a state of regeneration.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
||||
}
|
||||
@@ -1,2 +1,80 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
|
||||
/artifacts
|
||||
|
||||
/scripts/start.command
|
||||
/artifacts
|
||||
/ios
|
||||
/android
|
||||
# @generated expo-cli sync-e7dcf75f4e856f7b6f3239b3f3a7dd614ee755a8
|
||||
# The following patterns were generated by expo-cli
|
||||
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
build/
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
*.keystore
|
||||
!debug.keystore
|
||||
|
||||
# Bundle artifacts
|
||||
*.jsbundle
|
||||
|
||||
# CocoaPods
|
||||
/ios/Pods/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
web-build/
|
||||
dist/
|
||||
|
||||
# @end expo-cli
|
||||
@@ -1 +1,8 @@
|
||||
export { default } from './src/react-native-adjust/App';
|
||||
// export { default } from './src/react-native-quick-actions/App';
|
||||
// export { default } from './src/react-native-dynamic-app-icon/App';
|
||||
// export { default } from './src/react-native-google-cast/App';
|
||||
export { default } from './src/detox/App';
|
||||
// export { default } from './src/react-native-ble-plx/App';
|
||||
// export { default } from './src/react-native-webrtc/App';
|
||||
// export { default } from './src/android-jsc-intl/App';
|
||||
// export { default } from './src/ffmpeg-kit-react-native/App';
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "app",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"icon": "./assets/icons/winter.png",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
@@ -13,10 +13,12 @@
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "dev.bacon.app"
|
||||
"bundleIdentifier": "com.bacon.app"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
@@ -29,9 +31,7 @@
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"@config-plugins/react-native-adjust",
|
||||
"@config-plugins/react-native-callkeep",
|
||||
"expo-localization"
|
||||
"@config-plugins/detox"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
@@ -0,0 +1,6 @@
|
||||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
const iosName = "app";
|
||||
const emulator = process.env.CI ? "app" : "Pixel_3a_API_30";
|
||||
const simulator = "iPhone 11";
|
||||
const derivedDataPath = "ios/build";
|
||||
const sdk = "iphonesimulator";
|
||||
|
||||
module.exports = {
|
||||
testRunner: "jest",
|
||||
runnerConfig: require.resolve("./e2e/jest.config.js"),
|
||||
specs: "e2e",
|
||||
behavior: {
|
||||
init: {
|
||||
exposeGlobals: false,
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
"ios.release": {
|
||||
type: "ios.app",
|
||||
binaryPath: `${derivedDataPath}/Build/Products/Release-${sdk}/${iosName}.app`,
|
||||
build: `./scripts/build-detox-ios.sh ${iosName} Release YES`,
|
||||
},
|
||||
"ios.debug": {
|
||||
type: "ios.app",
|
||||
binaryPath: `${derivedDataPath}/Build/Products/Debug-${sdk}/${iosName}.app`,
|
||||
build: `./scripts/start-metro.sh && ./scripts/build-detox-ios.sh ${iosName} Debug`,
|
||||
},
|
||||
"android.debug": {
|
||||
type: "android.apk",
|
||||
binaryPath: "android/app/build/outputs/apk/debug/app-debug.apk",
|
||||
build:
|
||||
"./scripts/start-metro.sh && pushd android; ./gradlew app:assembleDebug app:assembleAndroidTest -DtestBuildType=debug; popd",
|
||||
},
|
||||
"android.release": {
|
||||
type: "android.apk",
|
||||
binaryPath: "android/app/build/outputs/apk/release/app-release.apk",
|
||||
build:
|
||||
"pushd android; ./gradlew app:assembleRelease app:assembleAndroidTest -DtestBuildType=release; popd",
|
||||
},
|
||||
},
|
||||
devices: {
|
||||
simulator: {
|
||||
type: "ios.simulator",
|
||||
device: {
|
||||
type: simulator,
|
||||
},
|
||||
},
|
||||
emulator: {
|
||||
type: "android.emulator",
|
||||
device: {
|
||||
avdName: emulator,
|
||||
},
|
||||
},
|
||||
},
|
||||
configurations: {
|
||||
"ios.sim.release": {
|
||||
device: "simulator",
|
||||
app: "ios.release",
|
||||
},
|
||||
"ios.sim.debug": {
|
||||
device: "simulator",
|
||||
app: "ios.debug",
|
||||
},
|
||||
"android.emu.debug": {
|
||||
device: "emulator",
|
||||
app: "android.debug",
|
||||
},
|
||||
"android.emu.release": {
|
||||
device: "emulator",
|
||||
app: "android.release",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { device } from "detox";
|
||||
import * as path from "path";
|
||||
|
||||
import { createCapture } from "../lib";
|
||||
|
||||
const projectRoot = path.resolve(__dirname, "../../");
|
||||
|
||||
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html
|
||||
const languages = [
|
||||
"en-US",
|
||||
// "nl-NL", "zh-CN"
|
||||
];
|
||||
|
||||
for (const locale of languages) {
|
||||
const { captureAsync, resetAppAsync } = createCapture({
|
||||
projectRoot,
|
||||
locale,
|
||||
});
|
||||
|
||||
describe(locale, () => {
|
||||
beforeAll(resetAppAsync);
|
||||
|
||||
beforeEach(async () => {
|
||||
await device.reloadReactNative();
|
||||
});
|
||||
|
||||
it("should load app", async () => {
|
||||
// await expect(element(by.id("welcome"))).toBeVisible();
|
||||
await captureAsync();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
const {
|
||||
DetoxCircusEnvironment,
|
||||
SpecReporter,
|
||||
WorkerAssignReporter,
|
||||
} = require("detox/runners/jest-circus");
|
||||
|
||||
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
|
||||
constructor(config, context) {
|
||||
super(config, context);
|
||||
|
||||
// Can be safely removed, if you are content with the default value (=300000ms)
|
||||
this.initTimeout = 300000;
|
||||
|
||||
// This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level.
|
||||
// This is strictly optional.
|
||||
this.registerListeners({
|
||||
SpecReporter,
|
||||
WorkerAssignReporter,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CustomDetoxEnvironment;
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
testEnvironment: require.resolve("./environment.js"),
|
||||
testRunner: require.resolve("jest-circus/runner"),
|
||||
testTimeout: 120000,
|
||||
testRegex: "/__tests__/.*(e2e)\\.[jt]sx?$",
|
||||
transform: {
|
||||
"^.+\\.[jt]sx?$": "ts-jest",
|
||||
},
|
||||
reporters: [require.resolve("detox/runners/jest/streamlineReporter")],
|
||||
verbose: true,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { device } from "detox";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { setStatusBar } from "./setStatusBar";
|
||||
|
||||
export function getOutputDir(projectRoot: string, locale: string) {
|
||||
const store = device.getPlatform() === "ios" ? "apple" : "google";
|
||||
return path.resolve(projectRoot, `merch/${store}/preview/${locale}/`);
|
||||
}
|
||||
|
||||
function resolveName(str: string) {
|
||||
if (str && device.getPlatform() === "ios" && typeof str === "string") {
|
||||
// Convert a name like: `E9EA2214-6B88-4223-9872-8232168088FB {"type":"iPhone 12 Pro Max"}`
|
||||
// to `iPhone 12 Pro Max`
|
||||
// Name is important for screenshot sorting
|
||||
const match = str.match(/"type"\s?:\s?"(.*)"}/i)?.[1];
|
||||
return match || str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export async function takeScreenshotAsync({
|
||||
info,
|
||||
projectRoot,
|
||||
index,
|
||||
locale,
|
||||
}: {
|
||||
projectRoot: string;
|
||||
locale: string;
|
||||
info?: string;
|
||||
index?: number;
|
||||
}) {
|
||||
const outputDir = getOutputDir(projectRoot, locale);
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
if (index == null) {
|
||||
index = fs.readdirSync(outputDir).length;
|
||||
}
|
||||
const deviceName = resolveName(device.name);
|
||||
|
||||
const name = `APP_${deviceName}-${index}`;
|
||||
const imgPath = path.resolve(outputDir, `${name}.png`);
|
||||
const imagePath = await device.takeScreenshot(name);
|
||||
await fs.promises.copyFile(imagePath, imgPath);
|
||||
}
|
||||
|
||||
export function createCapture({
|
||||
projectRoot,
|
||||
locale,
|
||||
}: {
|
||||
locale: string;
|
||||
projectRoot: string;
|
||||
}) {
|
||||
const captureAsync = () =>
|
||||
takeScreenshotAsync({
|
||||
projectRoot,
|
||||
locale,
|
||||
});
|
||||
|
||||
const clearAsync = async () => {
|
||||
// Clear old screen shots
|
||||
const outputDir = getOutputDir(projectRoot, locale);
|
||||
await fs.promises.rmdir(outputDir, { recursive: true }).catch(() => null);
|
||||
};
|
||||
|
||||
return {
|
||||
captureAsync,
|
||||
clearAsync,
|
||||
async resetAppAsync() {
|
||||
// Clear old screen shots
|
||||
// await clearAsync();
|
||||
// configure status bars
|
||||
|
||||
setStatusBar();
|
||||
|
||||
// reset expo-localization which doesn't account for simulators dynamically updating without resetting the phone.
|
||||
await device.terminateApp();
|
||||
|
||||
// launch with lang
|
||||
await device.launchApp({
|
||||
languageAndLocale: { locale, language: locale },
|
||||
launchArgs: { locale },
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { execSync } from "child_process";
|
||||
import { device } from "detox";
|
||||
|
||||
export async function setStatusBar() {
|
||||
if (device.getPlatform() === "ios") {
|
||||
// TODO: Set date on iPad - "Tue Jan 9"
|
||||
// assumes only one simulator is booted
|
||||
// use Apple's magic time "9:41" - new Date('1/9/2007 09:41').toISOString()
|
||||
execSync(
|
||||
"xcrun simctl status_bar booted override --time 9:41 --batteryState charged --batteryLevel 100 --wifiBars 3 --cellularMode active --cellularBars 4"
|
||||
);
|
||||
} else {
|
||||
// enter demo mode
|
||||
execSync("adb shell settings put global sysui_demo_allowed 1");
|
||||
// display time 6:00
|
||||
execSync(
|
||||
"adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1850"
|
||||
);
|
||||
// Display full mobile data with 4g type and no wifi
|
||||
execSync(
|
||||
"adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e level 4 -e datatype 4g -e wifi false"
|
||||
);
|
||||
// Hide notifications
|
||||
execSync(
|
||||
"adb shell am broadcast -a com.android.systemui.demo -e command notifications -e visible false"
|
||||
);
|
||||
// Show full battery but not in charging state
|
||||
execSync(
|
||||
"adb shell am broadcast -a com.android.systemui.demo -e command battery -e plugged false -e level 100"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,16 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
import "react-native-gesture-handler";
|
||||
import { registerRootComponent } from "expo";
|
||||
|
||||
import App from './App';
|
||||
import Constants from "expo-constants";
|
||||
|
||||
let App;
|
||||
// Only use the Detox test in CI
|
||||
if ((Constants.manifest.extra || {}).CI) {
|
||||
App = require("./src/detox/App").default;
|
||||
} else {
|
||||
App = require("./App").default;
|
||||
}
|
||||
// import App from "./App";
|
||||
|
||||
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||
// It also ensures that whether you load the app in Expo Go or in a native build,
|
||||
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 86 KiB |
@@ -0,0 +1,6 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require("@expo/metro-config");
|
||||
|
||||
const config = getDefaultConfig(__dirname);
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,36 +1,59 @@
|
||||
{
|
||||
"name": "@expo/app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"detox:clean": "detox clean-framework-cache && detox build-framework-cache",
|
||||
"build:ios": "detox build -c ios.sim.debug",
|
||||
"test:ios": "detox test -c ios.sim.debug",
|
||||
"e2e:ios": "npm run build:ios && npm run test:ios",
|
||||
"build:ios-release": "detox build -c ios.sim.release",
|
||||
"test:ios-release": "detox test -c ios.sim.release",
|
||||
"e2e:ios-release": "npm run build:ios-release && npm run test:ios-release",
|
||||
"build:android": "detox build -c android.emu.debug",
|
||||
"test:android": "detox test -c android.emu.debug",
|
||||
"e2e:android": "npm run build:android && npm run test:android",
|
||||
"build:android-release": "detox build -c android.emu.release",
|
||||
"test:android-release": "detox test -c android.emu.release --headless",
|
||||
"e2e:android-release": "npm run build:android-release && npm run test:android-release",
|
||||
"clean:android": "pushd android && ./gradlew clean && popd",
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/react-native-adjust": "workspace:*",
|
||||
"@config-plugins/react-native-callkeep": "workspace:*",
|
||||
"expo": "~56",
|
||||
"expo-localization": "~56.0.6",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"fbjs": "^0.8.18",
|
||||
"@config-plugins/detox": "^1.0.0",
|
||||
"expo": "^43.0.0",
|
||||
"expo-localization": "~11.0.0",
|
||||
"expo-splash-screen": "~0.13.5",
|
||||
"expo-status-bar": "~1.1.0",
|
||||
"expo-updates": "~0.10.13",
|
||||
"ffmpeg-kit-react-native": "^4.5.0",
|
||||
"i18n-js": "^3.8.0",
|
||||
"luxon": "^1.27.0",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-adjust": "^5.6.0",
|
||||
"react-native-callkeep": "^4.3.16",
|
||||
"react-native-safe-area-context": "~5.7.0"
|
||||
"react": "17.0.1",
|
||||
"react-native": "0.64.3",
|
||||
"react-native-reanimated": "~2.2.0",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "~3.8.0",
|
||||
"react-dom": "17.0.1",
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-web": "0.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/plugin-syntax-jsx": "^7.16.0",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@expo/metro-config": "~0.3.0",
|
||||
"@expo/xcpretty": "^4.1.0",
|
||||
"@types/jest": "^26.0.19",
|
||||
"@types/luxon": "^1.27.1",
|
||||
"babel-jest": "^29.5.0",
|
||||
"jest": "~29.7.0",
|
||||
"jest-circus": "^29.5.0",
|
||||
"ts-jest": "^29.0.5",
|
||||
"typescript": "~5.9.2"
|
||||
}
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-native": "~0.64.12",
|
||||
"babel-jest": "~25.2.6",
|
||||
"detox": "^19.1.0",
|
||||
"jest": "^26.5.0",
|
||||
"jest-circus": "^26.5.2",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script wraps the xcodebuild command and exits with non-zero if the build fails.
|
||||
#
|
||||
# This ensures that CI fails on the correct step instead of attempting to run Detox tests without a
|
||||
# build.
|
||||
|
||||
set -eu
|
||||
|
||||
iosName=$1
|
||||
# Debug or Release
|
||||
configuration=$2
|
||||
# YES or NO
|
||||
UseModernBuildSystem=${3:-"NO"}
|
||||
|
||||
xcodebuild \
|
||||
-workspace ios/$iosName.xcworkspace \
|
||||
-scheme $iosName \
|
||||
-configuration "$configuration" \
|
||||
-sdk iphonesimulator \
|
||||
-derivedDataPath "ios/build" \
|
||||
-UseModernBuildSystem="$UseModernBuildSystem" 2>&1 | npx excpretty ./
|
||||
|
||||
if [ "${PIPESTATUS[0]}" -ne "0" ]; then
|
||||
echo 'Build Failed'
|
||||
set +e
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'Build Succeeded'
|
||||
@@ -16,7 +16,7 @@ else
|
||||
commandFile=$(dirname "$0")/start.command
|
||||
cat > ${commandFile} << EOF
|
||||
cd "\$(dirname "\$0")/.."
|
||||
# Run 'npx expo start --help' to get more parameters
|
||||
# Run 'react-native start --help' to get more parameters
|
||||
yarn start --port ${port}
|
||||
EOF
|
||||
# execute the file in a new command line window
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
const currentDate = DateTime.fromObject({
|
||||
locale: "en-US",
|
||||
zone: "America/New_York",
|
||||
});
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "blue",
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 18, color: "white", textAlign: "center" }}>
|
||||
Intl support for Android, without Hermes 🤖
|
||||
</Text>
|
||||
<Text style={{ fontSize: 18, color: "white", textAlign: "center" }}>
|
||||
{currentDate.toISO()}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||
|
||||
import * as Localization from "expo-localization";
|
||||
import i18n from "i18n-js";
|
||||
// Set the key-value pairs for the different languages you want to support.
|
||||
i18n.translations = {
|
||||
"en-US": { welcome: "Welcome", hello: "Hello", world: "World" },
|
||||
"nl-NL": { welcome: "Welkom", hello: "Hallo", world: "Wereld" },
|
||||
"zh-CN": { welcome: "欢迎", hello: "你好", world: "世界" },
|
||||
};
|
||||
// Set the locale once at the beginning of your app.
|
||||
i18n.locale = Localization.locale;
|
||||
|
||||
export default function App() {
|
||||
const [greeting, setGreeting] = React.useState("");
|
||||
|
||||
if (greeting) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.text}>{greeting}!!!</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View testID="welcome" style={styles.container}>
|
||||
<Text style={styles.text}>
|
||||
{i18n.t("welcome")} {JSON.stringify(Localization.locales)}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
testID="hello_button"
|
||||
onPress={() => setGreeting(i18n.t("hello"))}
|
||||
>
|
||||
<Text style={styles.buttonText}>Say {i18n.t("hello")}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
testID="world_button"
|
||||
onPress={() => setGreeting(i18n.t("world"))}
|
||||
>
|
||||
<Text style={styles.buttonText}>Say {i18n.t("world")}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
text: {
|
||||
fontSize: 25,
|
||||
marginBottom: 30,
|
||||
},
|
||||
buttonText: {
|
||||
color: "blue",
|
||||
marginBottom: 20,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Session } from 'ffmpeg-kit-react-native';
|
||||
import React from 'react';
|
||||
import { ActivityIndicator, SafeAreaView, ScrollView, Text, View } from 'react-native';
|
||||
|
||||
import { useMediaInfo, useResolvedValue } from './useMediaInfo';
|
||||
|
||||
export default function App() {
|
||||
const session = useMediaInfo('http://techslides.com/demos/sample-videos/small.mp4');
|
||||
|
||||
if (!session) {
|
||||
return <View style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}><ActivityIndicator /><Text>Loading...</Text></View>
|
||||
}
|
||||
|
||||
return <SessionInfo session={session} />
|
||||
|
||||
}
|
||||
|
||||
function SessionInfo(props: { session: Session }) {
|
||||
const { session } = props;
|
||||
const duration = useResolvedValue(session.getDuration.bind(session));
|
||||
const output = useResolvedValue(session.getOutput.bind(session));
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ScrollView>
|
||||
<Text>Duration: {duration}</Text>
|
||||
<Text>Output: {output}</Text>
|
||||
</ScrollView>
|
||||
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { FFprobeKit, Session } from "ffmpeg-kit-react-native";
|
||||
import React from "react";
|
||||
|
||||
function useMounted() {
|
||||
const isMounted = React.useRef(true);
|
||||
React.useEffect(() => {
|
||||
isMounted.current = true;
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
return isMounted;
|
||||
}
|
||||
|
||||
export function useResolvedValue<T>(
|
||||
method: () => Promise<T>
|
||||
): [T | null, Error | null] {
|
||||
const [error, setError] = React.useState<Error | null>(null);
|
||||
const [value, setValue] = React.useState<T | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
method()
|
||||
.then((value) => {
|
||||
if (isMounted) {
|
||||
setValue(value);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (isMounted) {
|
||||
setError(error);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return [value, error];
|
||||
}
|
||||
|
||||
export function useMediaInfo(filePathOrUrl: string) {
|
||||
const [results, setResults] = React.useState<Session | undefined>(undefined);
|
||||
const isMounted = useMounted();
|
||||
React.useEffect(() => {
|
||||
FFprobeKit.getMediaInformationAsync(filePathOrUrl, async (session) => {
|
||||
if (isMounted) {
|
||||
setResults(session);
|
||||
}
|
||||
});
|
||||
}, [filePathOrUrl]);
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, Text, StyleSheet } from 'react-native';
|
||||
import { Adjust, AdjustConfig } from 'react-native-adjust';
|
||||
|
||||
export default function App() {
|
||||
React.useEffect(() => {
|
||||
// https://github.com/adjust/react_native_sdk/blob/bd4fe59403a57599767606b2bcaa116852df973d/example/App.js#L45
|
||||
const adjustConfig = new AdjustConfig('APP_TOKEN', AdjustConfig.EnvironmentSandbox);
|
||||
Adjust.enable();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<Text>Adjust SDK</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import useBluetoothDevices, { useManager, useManagerState } from './useBluetoothDevices';
|
||||
|
||||
export default function App() {
|
||||
const bleManager = useManager();
|
||||
const state = useManagerState(bleManager);
|
||||
|
||||
console.log('Info:', state);
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'blue'}}>
|
||||
<Text style={{fontSize: 18, color: 'white'}}>State: {state}</Text>
|
||||
{state === 'PoweredOn' && <DeviceScanner bleManager={bleManager} />}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function DeviceScanner({ bleManager }) {
|
||||
const [devices] = useBluetoothDevices(bleManager);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log(devices);
|
||||
}, [devices]);
|
||||
|
||||
return (
|
||||
<View>
|
||||
{devices.filter(({name}) => name).map((device, index) => (<Text key={`-${index}`} style={{fontSize: 14, color: 'white'}}>{device.name}</Text>))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { BleManager, State, Device } from "react-native-ble-plx";
|
||||
import { uniqBy } from "lodash";
|
||||
|
||||
export function useManager() {
|
||||
return useMemo(() => new BleManager(), []);
|
||||
}
|
||||
|
||||
export function useManagerState(bleManager: BleManager): State | null {
|
||||
const [state, setState] = useState<State | null>(null);
|
||||
const isMounted = useMounted();
|
||||
|
||||
useEffect(() => {
|
||||
if (!bleManager) return;
|
||||
|
||||
bleManager.onStateChange((state) => {
|
||||
if (isMounted.current) {
|
||||
setState(state);
|
||||
}
|
||||
}, true);
|
||||
}, [bleManager]);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function useMounted() {
|
||||
const isMounted = useRef(true);
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
return isMounted;
|
||||
}
|
||||
|
||||
export function useBluetoothDevices(
|
||||
bleManager: BleManager
|
||||
): [Device[], null | string] {
|
||||
const isMoutned = useMounted();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [devices, setDevices] = useState<Device[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
bleManager.startDeviceScan(
|
||||
[],
|
||||
{ allowDuplicates: false },
|
||||
(e, scannedDevice) => {
|
||||
if (!isMoutned.current) return;
|
||||
if (e) {
|
||||
setError(e.message);
|
||||
}
|
||||
|
||||
if (!scannedDevice) return;
|
||||
|
||||
setDevices((oldDevices) =>
|
||||
uniqBy([...oldDevices, scannedDevice], "id")
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
bleManager.stopDeviceScan();
|
||||
};
|
||||
}, [bleManager]);
|
||||
|
||||
return [devices, error];
|
||||
}
|
||||
|
||||
export default useBluetoothDevices;
|
||||
@@ -0,0 +1,142 @@
|
||||
import Entypo from '@expo/vector-icons/Entypo';
|
||||
import React from 'react';
|
||||
import { Image, SafeAreaView, Text, View } from 'react-native';
|
||||
import AppIcon from 'react-native-dynamic-app-icon';
|
||||
import TouchableBounce from 'react-native/Libraries/Components/Touchable/TouchableBounce';
|
||||
|
||||
function useIconName() {
|
||||
const [icon, _setIcon] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
let isMounted = true;
|
||||
AppIcon.getIconName((result) => {
|
||||
if (isMounted) _setIcon(result.iconName);
|
||||
});
|
||||
return () => (isMounted = false);
|
||||
}, []);
|
||||
|
||||
const setIcon = React.useCallback(
|
||||
(icon) => {
|
||||
AppIcon.setAppIcon(icon);
|
||||
_setIcon(icon || null);
|
||||
},
|
||||
[_setIcon]
|
||||
);
|
||||
return [icon === "default" ? null : icon, setIcon];
|
||||
}
|
||||
|
||||
|
||||
const icons = [
|
||||
{ name: "Auto", source: require("../../assets/icons/winter.png"), iconId: null },
|
||||
{ name: "Spring", source: require("../../assets/icons/spring.png"), iconId: '0' },
|
||||
{ name: "Summer", source: require("../../assets/icons/summer.png"), iconId: '1' },
|
||||
{ name: "Autumn", source: require("../../assets/icons/autumn.png"), iconId: '2' },
|
||||
{ name: "Fall", source: require("../../assets/icons/fall.png"), iconId: '3' },
|
||||
{ name: "Solstice", source: require("../../assets/icons/solstice.png"), iconId: '4' },
|
||||
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [_icon, setIcon] = useIconName();
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View style={{ width: "100%" }}>
|
||||
{icons.map((icon, index) => {
|
||||
return (
|
||||
<Item
|
||||
onPress={() => {
|
||||
setIcon(icon.iconId);
|
||||
}}
|
||||
isSelected={icon.iconId === _icon}
|
||||
name={icon.name}
|
||||
source={icon.source}
|
||||
key={String(index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
function Item({ ...props }) {
|
||||
return (
|
||||
<TouchableBounce
|
||||
onPress={props.onPress}
|
||||
style={{ marginVertical: 8, marginHorizontal: 24 }}
|
||||
>
|
||||
<View
|
||||
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "stretch",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 4,
|
||||
},
|
||||
shadowOpacity: props.isSelected ? 0.35 : 0.2,
|
||||
shadowRadius: 8.46,
|
||||
|
||||
backgroundColor: props.isSelected ? "#000" : "#F1F1F1",
|
||||
elevation: 9,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 20,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={props.source}
|
||||
style={{
|
||||
width: 64,
|
||||
aspectRatio: 1,
|
||||
resizeMode: "cover",
|
||||
borderRadius: 16,
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
marginLeft: 20,
|
||||
fontSize: 18,
|
||||
fontWeight: "700",
|
||||
color: props.isSelected ? "white" : "black",
|
||||
}}
|
||||
>
|
||||
{props.name}
|
||||
</Text>
|
||||
</View>
|
||||
{props.isSelected && (
|
||||
<Entypo
|
||||
style={{ marginRight: 8 }}
|
||||
name="check"
|
||||
size={20}
|
||||
color="white"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
import { View, Text, Button } from "react-native";
|
||||
import GoogleCast, {
|
||||
CastState,
|
||||
CastButton,
|
||||
useCastState,
|
||||
useRemoteMediaClient,
|
||||
} from "react-native-google-cast";
|
||||
|
||||
export default function HomeScreen() {
|
||||
const castState = useCastState();
|
||||
const client = useRemoteMediaClient();
|
||||
|
||||
GoogleCast.showIntroductoryOverlay();
|
||||
|
||||
const isConnected = castState === CastState.CONNECTED;
|
||||
|
||||
function cast() {
|
||||
client
|
||||
.loadMedia({
|
||||
mediaInfo: {
|
||||
contentUrl:
|
||||
"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/BigBuckBunny.mp4",
|
||||
contentType: "video/mp4",
|
||||
metadata: {
|
||||
images: [
|
||||
{
|
||||
url:
|
||||
"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/images/480x270/BigBuckBunny.jpg",
|
||||
},
|
||||
],
|
||||
title: "Big Buck Bunny",
|
||||
subtitle:
|
||||
"A large and lovable rabbit deals with three tiny bullies, led by a flying squirrel, who are determined to squelch his happiness.",
|
||||
studio: "Blender Foundation",
|
||||
type: "movie",
|
||||
},
|
||||
streamDuration: 596, // seconds
|
||||
},
|
||||
startTime: 10, // seconds
|
||||
})
|
||||
.then(console.log)
|
||||
.catch(console.warn);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<Text>State: {castState}</Text>
|
||||
<CastButton style={{ width: 24, height: 24 }} />
|
||||
<Button
|
||||
title="Overlay"
|
||||
onPress={() => {
|
||||
GoogleCast.showIntroductoryOverlay();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="Expanded Controls"
|
||||
onPress={() => {
|
||||
GoogleCast.showExpandedControls();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Cast"
|
||||
disabled={!isConnected}
|
||||
onPress={() => {
|
||||
cast();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="Pause"
|
||||
disabled={!isConnected}
|
||||
onPress={() => {
|
||||
// cast()
|
||||
client.pause();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="Disconnect"
|
||||
disabled={!isConnected}
|
||||
onPress={() => {
|
||||
GoogleCast.sessionManager.endCurrentSession(true);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, StyleSheet, useWindowDimensions } from 'react-native';
|
||||
import Pdf from 'react-native-pdf';
|
||||
|
||||
const source = {
|
||||
uri: "https://samples.leanpub.com/thereactnativebook-sample.pdf",
|
||||
cache: true,
|
||||
};
|
||||
export default function App() {
|
||||
const { width, height } = useWindowDimensions()
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<Pdf
|
||||
source={source}
|
||||
onLoadComplete={(numberOfPages, filePath) => {
|
||||
console.log(`Number of pages: ${numberOfPages}`);
|
||||
}}
|
||||
onPageChanged={(page, numberOfPages) => {
|
||||
console.log(`Current page: ${page}`);
|
||||
}}
|
||||
onError={(error) => {
|
||||
console.log(error);
|
||||
}}
|
||||
onPressLink={(uri) => {
|
||||
console.log(`Link pressed: ${uri}`);
|
||||
}}
|
||||
style={{ flex: 1, width, height }}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
},
|
||||
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import QuickActions from "react-native-quick-actions";
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
import { DeviceEventEmitter } from "react-native";
|
||||
|
||||
function useQuickAction(): QuickActions.ShortcutItem | null {
|
||||
const [item, setItem] = React.useState<QuickActions.ShortcutItem | null>(null);
|
||||
const isMounted = React.useRef(true)
|
||||
React.useEffect(() => {
|
||||
QuickActions.popInitialAction().then(action => {
|
||||
if (isMounted.current) setItem(action);
|
||||
})
|
||||
const sub = DeviceEventEmitter.addListener("quickActionShortcut", data => {
|
||||
if (isMounted.current) {
|
||||
setItem(data);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
sub.remove();
|
||||
}
|
||||
}, []);
|
||||
return item;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const action = useQuickAction();
|
||||
|
||||
React.useEffect(() => {
|
||||
QuickActions.setShortcutItems([
|
||||
{
|
||||
title: 'Play Song',
|
||||
subtitle: 'NDA - Billie Eilish',
|
||||
icon: 'Play',
|
||||
type: 'alpha',
|
||||
} as any,
|
||||
{
|
||||
title: 'Shuffle',
|
||||
icon: 'Shuffle',
|
||||
type: 'beta',
|
||||
} as any,
|
||||
{
|
||||
title: 'Like Song',
|
||||
icon: 'Love',
|
||||
type: 'gamma',
|
||||
} as any,
|
||||
])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<Text>Quick Actions</Text>
|
||||
{action && <Text>{JSON.stringify(action, null, 2)}</Text>}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
export default App;
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Button, SafeAreaView, StatusBar, StyleSheet, View } from 'react-native';
|
||||
import { mediaDevices, RTCView } from 'react-native-webrtc';
|
||||
|
||||
const App = () => {
|
||||
const [stream, setStream] = useState(null);
|
||||
const start = async () => {
|
||||
console.log('start');
|
||||
if (!stream) {
|
||||
let s;
|
||||
try {
|
||||
s = await mediaDevices.getUserMedia({ video: true });
|
||||
setStream(s);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
const stop = () => {
|
||||
console.log('stop');
|
||||
if (stream) {
|
||||
stream.release();
|
||||
setStream(null);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<StatusBar barStyle="dark-content" />
|
||||
<SafeAreaView style={styles.body}>
|
||||
{
|
||||
stream &&
|
||||
<RTCView
|
||||
streamURL={stream.toURL()}
|
||||
style={styles.stream} />
|
||||
}
|
||||
<View
|
||||
style={styles.footer}>
|
||||
<Button
|
||||
title = "Start"
|
||||
onPress = {start} />
|
||||
<Button
|
||||
title = "Stop"
|
||||
onPress = {stop} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
body: {
|
||||
...StyleSheet.absoluteFillObject
|
||||
},
|
||||
stream: {
|
||||
flex: 1
|
||||
},
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
});
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,3 @@
|
||||
const { createWebpackConfigAsync } = require("expo-yarn-workspaces/webpack");
|
||||
|
||||
module.exports = createWebpackConfigAsync;
|
||||
@@ -1,49 +0,0 @@
|
||||
appId: dev.bacon.applesettings
|
||||
---
|
||||
# Kill the Settings app to ensure a clean state
|
||||
- killApp: 'com.apple.Preferences'
|
||||
|
||||
# Launch the app
|
||||
- launchApp
|
||||
|
||||
# Verify main UI elements are visible
|
||||
- assertVisible:
|
||||
id: 'name-input'
|
||||
- assertVisible:
|
||||
id: 'enabled-switch'
|
||||
- assertVisible:
|
||||
id: 'slider'
|
||||
|
||||
# Clear any existing text and type a new value
|
||||
- tapOn:
|
||||
id: 'name-input'
|
||||
- eraseText: 50
|
||||
- inputText: 'Maestro'
|
||||
|
||||
# Toggle the switch on (default is off)
|
||||
- tapOn:
|
||||
id: 'enabled-switch'
|
||||
|
||||
# Open iOS Settings for this app
|
||||
- tapOn: 'Launch Settings'
|
||||
|
||||
# Navigate to the app's settings page
|
||||
- scrollUntilVisible:
|
||||
element: 'Apps'
|
||||
direction: DOWN
|
||||
- tapOn: 'Apps'
|
||||
- scrollUntilVisible:
|
||||
element: 'apple-settings'
|
||||
direction: DOWN
|
||||
- tapOn: 'apple-settings'
|
||||
- scrollUntilVisible:
|
||||
element: 'apple-settings Settings'
|
||||
direction: DOWN
|
||||
|
||||
# Verify updated values in the iOS Settings app
|
||||
- assertVisible: 'Maestro'
|
||||
|
||||
# Verify the Enabled toggle is on (checked = true when on)
|
||||
- assertVisible:
|
||||
id: 'PSToggleSwitchSpecifier.enabled_preference.Enabled'
|
||||
checked: true
|
||||
@@ -1,123 +0,0 @@
|
||||
import withAppleSettings, {
|
||||
ChildPane,
|
||||
Group,
|
||||
RadioGroup,
|
||||
Slider,
|
||||
Switch,
|
||||
TextField,
|
||||
Title,
|
||||
} from '@config-plugins/apple-settings';
|
||||
import { MultiValue } from '@config-plugins/apple-settings/build/models';
|
||||
import { ConfigContext, ExpoConfig } from 'expo/config';
|
||||
import path from 'node:path';
|
||||
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
function withWhiteLabel(config: Partial<ExpoConfig>) {
|
||||
if (!config.extra) config.extra = {};
|
||||
// Expose CI env variables to the app
|
||||
config.extra.CI = process.env.CI;
|
||||
|
||||
if (!config.ios) config.ios = {};
|
||||
config.ios.bundleIdentifier = appId;
|
||||
if (!config.android) config.android = {};
|
||||
config.android.package = appId;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {
|
||||
config = withWhiteLabel(config);
|
||||
|
||||
config = withAppleSettings(config as ExpoConfig, {
|
||||
// The name of the .plist file to generate. Root is the default and must be provided.
|
||||
Root: {
|
||||
// The locales object is optional. If provided, it will be used to generate the localized .strings files.
|
||||
locales: {
|
||||
// The Apple locale code. This will be used to generate the .strings file.
|
||||
en: {
|
||||
// Name of the localized key.
|
||||
Name: 'Text Field',
|
||||
},
|
||||
},
|
||||
// The page object is required. It will be used to generate the .plist file.
|
||||
// The contents will be converted directly to plist.
|
||||
page: {
|
||||
// The `PreferenceSpecifiers` defines the UI elements to generate.
|
||||
PreferenceSpecifiers: [
|
||||
Title({
|
||||
title: 'Title',
|
||||
value: 'Default Title',
|
||||
key: 'title_preference',
|
||||
}),
|
||||
// Helper models can be used to generate the UI elements using
|
||||
// an API that's similar to React Native.
|
||||
TextField({
|
||||
title: 'Name',
|
||||
key: 'name_preference',
|
||||
value: '',
|
||||
keyboardType: 'Alphabet',
|
||||
autoCapitalize: 'None',
|
||||
autoCorrect: 'No',
|
||||
}),
|
||||
Switch({
|
||||
title: 'Enabled',
|
||||
key: 'enabled_preference',
|
||||
value: true,
|
||||
}),
|
||||
Slider({
|
||||
key: 'slider_preference',
|
||||
value: 0.5,
|
||||
}),
|
||||
// Broken https://forums.developer.apple.com/forums/thread/764519
|
||||
// RadioGroup({
|
||||
// value: "option1",
|
||||
// key: "radio_preference",
|
||||
// items: [
|
||||
// {
|
||||
// title: "Option 1",
|
||||
// value: "option1",
|
||||
// },
|
||||
|
||||
// {
|
||||
// title: "Option 2",
|
||||
// value: "option2",
|
||||
// },
|
||||
// ],
|
||||
// }),
|
||||
// MultiValue({
|
||||
// title: "Multi Value",
|
||||
// key: "multi_value_preference",
|
||||
// value: "alpha",
|
||||
// items: [
|
||||
// {
|
||||
// title: "Alpha",
|
||||
// value: "alpha",
|
||||
// short: "α",
|
||||
// },
|
||||
// {
|
||||
// title: "Beta",
|
||||
// value: "beta",
|
||||
// short: "β",
|
||||
// },
|
||||
// {
|
||||
// title: "Delta",
|
||||
// value: "delta",
|
||||
// short: "Δ",
|
||||
// },
|
||||
// {
|
||||
// title: "Omega",
|
||||
// value: "omega",
|
||||
// short: "Ω",
|
||||
// },
|
||||
// ],
|
||||
// }),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "apple-settings",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"platforms": ["ios"]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "@config-plugins-app/apple-settings",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android",
|
||||
"e2e": "maestro test .maestro/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/apple-settings": "workspace:*",
|
||||
"@react-native-community/slider": "5.2.0",
|
||||
"@react-native-picker/picker": "2.11.4",
|
||||
"expo": "~56",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3"
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Button, Linking, StyleSheet, View } from 'react-native';
|
||||
|
||||
import {
|
||||
SettingsRadioGroup,
|
||||
SettingsSlider,
|
||||
SettingsSwitch,
|
||||
SettingsTextInput,
|
||||
SettingsTitle,
|
||||
} from './SettingsViews';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* <SettingsTitle settingsKey="title_preference" /> */}
|
||||
<SettingsTextInput
|
||||
testID="name-input"
|
||||
style={{
|
||||
height: 40,
|
||||
borderColor: 'gray',
|
||||
borderWidth: 1,
|
||||
padding: 8,
|
||||
}}
|
||||
settingsKey="name_preference"
|
||||
/>
|
||||
<SettingsSwitch testID="enabled-switch" settingsKey="enabled_preference" />
|
||||
<SettingsSlider testID="slider" settingsKey="slider_preference" />
|
||||
<View />
|
||||
{/* Broken https://forums.developer.apple.com/forums/thread/764519 */}
|
||||
{/*<SettingsRadioGroup settingsKey="radio_preference">*/}
|
||||
{/* <SettingsRadioGroup.Item label="Option 1" value="option1" />*/}
|
||||
{/* <SettingsRadioGroup.Item label="Option 2" value="option2" />*/}
|
||||
{/*</SettingsRadioGroup>*/}
|
||||
<View />
|
||||
<Button
|
||||
title="Launch Settings"
|
||||
onPress={() => {
|
||||
void Linking.openSettings();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'stretch',
|
||||
paddingHorizontal: 56,
|
||||
gap: 12,
|
||||
},
|
||||
value: {
|
||||
fontSize: 24,
|
||||
marginVertical: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default App;
|
||||
@@ -1,79 +0,0 @@
|
||||
import Slider from '@react-native-community/slider';
|
||||
import { Picker } from '@react-native-picker/picker';
|
||||
import React from 'react';
|
||||
import { Settings, Switch, Text, TextInput } from 'react-native';
|
||||
|
||||
export function useSetting<T = string>(key: string): [T, (value: T) => void] {
|
||||
const [value, setValue] = React.useState<T>(() => Settings.get(key));
|
||||
React.useEffect(() => {
|
||||
let isMounted = true;
|
||||
const callback = Settings.watchKeys(key, () => {
|
||||
if (isMounted) {
|
||||
setValue(Settings.get(key));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
Settings.clearWatch(callback);
|
||||
isMounted = false;
|
||||
};
|
||||
}, [key]);
|
||||
|
||||
return [
|
||||
value,
|
||||
(value) => {
|
||||
Settings.set({ [key]: value });
|
||||
setValue(value);
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function SettingsSlider({
|
||||
settingsKey,
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof Slider>,
|
||||
'selectedValue' | 'onValueChange'
|
||||
>) {
|
||||
const [value, setValue] = useSetting<number>(settingsKey);
|
||||
return <Slider {...props} value={value ?? 0} onValueChange={setValue} />;
|
||||
}
|
||||
|
||||
export function SettingsRadioGroup({
|
||||
settingsKey,
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof Picker>,
|
||||
'selectedValue' | 'onValueChange'
|
||||
>) {
|
||||
const [value, setValue] = useSetting<string>(settingsKey);
|
||||
return <Picker {...props} selectedValue={value ?? 'option1'} onValueChange={setValue} />;
|
||||
}
|
||||
|
||||
SettingsRadioGroup.Item = Picker.Item;
|
||||
|
||||
export function SettingsSwitch({
|
||||
settingsKey,
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<React.ComponentProps<typeof Switch>, 'value' | 'onValueChange'>) {
|
||||
const [value, setValue] = useSetting<boolean>(settingsKey);
|
||||
return <Switch {...props} value={!!value} onValueChange={setValue} />;
|
||||
}
|
||||
|
||||
export function SettingsTitle({
|
||||
settingsKey,
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<React.ComponentProps<typeof Text>, 'children'>) {
|
||||
const [value] = useSetting<string>(settingsKey);
|
||||
return <Text {...props} children={value} />;
|
||||
}
|
||||
|
||||
export function SettingsTextInput({
|
||||
settingsKey,
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof TextInput>,
|
||||
'value' | 'onChangeText'
|
||||
>) {
|
||||
const [value, setValue] = useSetting<string>(settingsKey);
|
||||
return <TextInput {...props} value={value} onChangeText={setValue} />;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {},
|
||||
"extends": "expo/tsconfig.base"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
// Expose CI env variables to the app
|
||||
config.extra.CI = process.env.CI;
|
||||
|
||||
if (!config.ios) config.ios = {};
|
||||
config.ios.bundleIdentifier = appId;
|
||||
if (!config.android) config.android = {};
|
||||
config.android.package = appId;
|
||||
return config;
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "stickers",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"platforms": ["ios"],
|
||||
"plugins": [
|
||||
[
|
||||
"@config-plugins/ios-stickers",
|
||||
{
|
||||
"name": "Stickers",
|
||||
"stickerBundleId": "dev.bacon.iosstickers.stickers",
|
||||
"icon": "./assets/imessage-icon.png",
|
||||
"columns": 4,
|
||||
"stickers": [
|
||||
"./assets/stickers/annoyed.png",
|
||||
"./assets/stickers/cuddle.png",
|
||||
"./assets/stickers/crazy.png",
|
||||
"./assets/stickers/cant.png",
|
||||
"./assets/stickers/donno.png",
|
||||
"./assets/stickers/cozzy.png",
|
||||
"./assets/stickers/dork.png",
|
||||
"./assets/stickers/focus.png",
|
||||
"./assets/stickers/avocool.png",
|
||||
"./assets/stickers/coder-girl.png",
|
||||
"./assets/stickers/teapot.png"
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "@config-plugins-app/ios-stickers",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/ios-stickers": "workspace:*",
|
||||
"expo": "~56",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>Open iMessage to find the app</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {},
|
||||
"extends": "expo/tsconfig.base"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
// Expose CI env variables to the app
|
||||
config.extra.CI = process.env.CI;
|
||||
|
||||
if (!config.ios) config.ios = {};
|
||||
config.ios.bundleIdentifier = appId;
|
||||
if (!config.android) config.android = {};
|
||||
config.android.package = appId;
|
||||
return config;
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "branch",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"splash": {
|
||||
"image": "https://github.com/expo.png"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"@config-plugins/react-native-branch",
|
||||
{
|
||||
"apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@config-plugins-app/react-native-branch",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/react-native-branch": "workspace:*",
|
||||
"expo": "~56",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-branch": "^6.10.0"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>Branch</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {},
|
||||
"extends": "expo/tsconfig.base"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
// Expose CI env variables to the app
|
||||
config.extra.CI = process.env.CI;
|
||||
|
||||
if (!config.ios) config.ios = {};
|
||||
config.ios.bundleIdentifier = appId;
|
||||
if (!config.android) config.android = {};
|
||||
config.android.package = appId;
|
||||
return config;
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "pdf",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"splash": {
|
||||
"image": "https://github.com/expo.png"
|
||||
},
|
||||
"plugins": ["@config-plugins/react-native-blob-util", "@config-plugins/react-native-pdf"]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "@config-plugins-app/react-native-pdf",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/react-native-blob-util": "workspace:*",
|
||||
"@config-plugins/react-native-pdf": "workspace:*",
|
||||
"expo": "~56",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-blob-util": "^0.24.9",
|
||||
"react-native-pdf": "^7.0.4"
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { SafeAreaView, StyleSheet, useWindowDimensions } from 'react-native';
|
||||
import Pdf from 'react-native-pdf';
|
||||
|
||||
const source = {
|
||||
uri: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
||||
cache: true,
|
||||
};
|
||||
export default function App() {
|
||||
const { width, height } = useWindowDimensions();
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<Pdf
|
||||
trustAllCerts={false}
|
||||
source={source}
|
||||
onLoadComplete={(numberOfPages, filePath) => {
|
||||
console.log(`Number of pages: ${numberOfPages}`);
|
||||
}}
|
||||
onPageChanged={(page, numberOfPages) => {
|
||||
console.log(`Current page: ${page}`);
|
||||
}}
|
||||
onError={(error) => {
|
||||
console.log(error);
|
||||
}}
|
||||
onPressLink={(uri) => {
|
||||
console.log(`Link pressed: ${uri}`);
|
||||
}}
|
||||
style={{ flex: 1, width, height }}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
// Expose CI env variables to the app
|
||||
config.extra.CI = process.env.CI;
|
||||
|
||||
if (!config.ios) config.ios = {};
|
||||
config.ios.bundleIdentifier = appId;
|
||||
if (!config.android) config.android = {};
|
||||
config.android.package = appId;
|
||||
return config;
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "siri xcut",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"platforms": ["ios"],
|
||||
"splash": {
|
||||
"image": "https://github.com/expo.png"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"@config-plugins/react-native-siri-shortcut",
|
||||
[
|
||||
"com.github.gustash.SiriShortcutsModuleExample.sayHello",
|
||||
"com.github.gustash.SiriShortcutsModuleExample.somethingElse"
|
||||
]
|
||||
]
|
||||
],
|
||||
"ios": {
|
||||
"appleTeamId": "7PSDAY43Y6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@config-plugins-app/react-native-siri-shortcut",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/react-native-siri-shortcut": "workspace:*",
|
||||
"expo": "~56",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-siri-shortcut": "^3.2.4"
|
||||
}
|
||||
}
|
||||