mirror of
https://github.com/expo/config-plugins.git
synced 2026-11-13 20:28:07 -10:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c15065eb9 | ||
|
|
d299fbb361 | ||
|
|
3765a79aac | ||
|
|
765211130a | ||
|
|
a7cc2b2fed | ||
|
|
5d9597dbd6 | ||
|
|
2b36569912 | ||
|
|
e83c44c05e | ||
|
|
1fe140bae4 | ||
|
|
4a27ef455a | ||
|
|
9d8dccc6fc | ||
|
|
bced4c7b0d | ||
|
|
af26cb0d15 | ||
|
|
300770f462 | ||
|
|
6d6770068f | ||
|
|
c8547c07ba | ||
|
|
f7a291fc55 | ||
|
|
fc33478f00 | ||
|
|
a48bb42c19 | ||
|
|
c6292b104a | ||
|
|
3ea7b43e64 | ||
|
|
d971f378d7 | ||
|
|
a2fdafe0d9 | ||
|
|
17c928a7c6 | ||
|
|
93b0fda31b | ||
|
|
27cdb03c04 | ||
|
|
c1150cb723 | ||
|
|
639473d539 | ||
|
|
6a850e7902 | ||
|
|
1de01f582c | ||
|
|
69c2af5b87 | ||
|
|
bd1ffe70c5 | ||
|
|
45c6742c6b | ||
|
|
9412250df6 | ||
|
|
bd70addec7 | ||
|
|
5deff9c0a0 | ||
|
|
566f54e785 | ||
|
|
59f94769fa | ||
|
|
c30e5baa66 | ||
|
|
480485b5aa | ||
|
|
700dbf00dd | ||
|
|
3091b2236b | ||
|
|
ff43a0ea09 | ||
|
|
00197c766b | ||
|
|
5ec55510bc | ||
|
|
31975371cf | ||
|
|
0a1d687d61 |
No files matched your search
@@ -29,10 +29,7 @@ body:
|
||||
attributes:
|
||||
label: Config Plugin
|
||||
options:
|
||||
- '@config-plugins/android-jsc-intl'
|
||||
- '@config-plugins/apple-settings'
|
||||
- '@config-plugins/detox'
|
||||
- '@config-plugins/ffmpeg-kit-react-native'
|
||||
- '@config-plugins/ios-stickers'
|
||||
- '@config-plugins/react-native-adjust'
|
||||
- '@config-plugins/react-native-blob-util'
|
||||
@@ -62,7 +59,7 @@ body:
|
||||
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
|
||||
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:
|
||||
|
||||
@@ -6,12 +6,6 @@ contact_links:
|
||||
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: E2E tests and automation for mobile
|
||||
name: 📦 detox issues
|
||||
url: https://github.com/wix/Detox/issues
|
||||
- about: FFmpeg Kit for React Native
|
||||
name: 📦 ffmpeg-kit-react-native issues
|
||||
url: https://github.com/arthenica/ffmpeg-kit/issues
|
||||
- about: Adjust React Native SDK
|
||||
name: 📦 react-native-adjust issues
|
||||
url: https://github.com/adjust/react_native_sdk/issues
|
||||
@@ -20,6 +14,10 @@ contact_links:
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "⭐️ Config Plugin Request"
|
||||
description: "Suggest an idea for a new Expo Config Plugin."
|
||||
name: '⭐️ Config Plugin Request'
|
||||
description: 'Suggest an idea for a new Expo Config Plugin.'
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
🚨 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(detox): add new step
|
||||
🚨 Example: feat(ios-stickers): add new step
|
||||
-->
|
||||
|
||||
# Why
|
||||
|
||||
@@ -5,16 +5,34 @@ on:
|
||||
branches: [main]
|
||||
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:
|
||||
- uses: actions/checkout@v2
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: npx remark --use validate-links . .github
|
||||
- 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
|
||||
+30
-46
@@ -2,63 +2,47 @@ name: Test Packages
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["18"]
|
||||
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@v4
|
||||
with:
|
||||
path: "*"
|
||||
key: v2-${{ github.sha }}-${{ matrix.node }}
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: ["18"]
|
||||
package: [
|
||||
# Manually add packages here
|
||||
detox,
|
||||
ffmpeg-kit-react-native,
|
||||
ios-stickers,
|
||||
react-native-blob-util,
|
||||
react-native-branch,
|
||||
react-native-siri-shortcut,
|
||||
react-native-pdf,
|
||||
]
|
||||
name: Test ${{ matrix.package }} on Node ${{ matrix.node }}
|
||||
node: ['22', '24']
|
||||
name: Build and test on Node ${{ matrix.node }}
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
- name: 👀 Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
path: "*"
|
||||
key: v2-${{ github.sha }}-${{ matrix.node }}
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v1
|
||||
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: ${{ matrix.node }}
|
||||
- name: Lint ${{ matrix.package }}
|
||||
run: yarn lint --max-warnings 0
|
||||
working-directory: packages/${{ matrix.package }}
|
||||
- name: Test ${{ matrix.package }}
|
||||
run: yarn test
|
||||
working-directory: packages/${{ matrix.package }}
|
||||
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
|
||||
env:
|
||||
CI: true
|
||||
EXPO_DEBUG: true
|
||||
+5
-2
@@ -9,7 +9,7 @@ web-build
|
||||
# Node
|
||||
node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
pnpm-debug.log
|
||||
|
||||
# Ruby
|
||||
.direnv
|
||||
@@ -48,6 +48,9 @@ xcuserdata
|
||||
.project
|
||||
.settings
|
||||
|
||||
# WebStorm
|
||||
.idea
|
||||
|
||||
# VSCode
|
||||
.history/
|
||||
|
||||
@@ -63,4 +66,4 @@ packages/@*/*/build/**
|
||||
apps/*/ios/*
|
||||
apps/*/android/*
|
||||
apps/*/web-build/*
|
||||
apps/*/dist/*
|
||||
apps/*/dist/*
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$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,107 @@
|
||||
# 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
|
||||
```
|
||||
+18
-5
@@ -5,7 +5,7 @@
|
||||
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 `yarn` in the root directory to generate the build files.
|
||||
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.
|
||||
@@ -13,15 +13,28 @@ To submit a pull request:
|
||||
|
||||
## Creating a plugin
|
||||
|
||||
- Plugins can be generated by running `yarn gen`.
|
||||
- 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: `yarn update-dependabot-config`
|
||||
- Update the issue template: `yarn update-issue-template`
|
||||
- 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
|
||||
|
||||
All publishing should be handled automatically whenever code is merged to `main`.
|
||||
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,68 +1,16 @@
|
||||
# Expo Config Plugins
|
||||
|
||||
- A collection of [Expo config plugins](https://docs.expo.dev/guides/config-plugins/) for configuring the `npx expo prebuild` command.
|
||||
- This repo is to Expo config plugins as [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) is to TypeScript.
|
||||
- We prefer packages ship their own Expo config plugin (to ensure versioning is aligned), but if they haven't adopted the system yet, the community can add a package here.
|
||||
- All packages here are community maintained. Feel free to improve docs, packages, tests, etc.
|
||||
- We maintain a 1-1 mapping of **native packages** ⇔ `@config-plugins/*`. This means there should be no general utility packages here -- those are better suited for a different repo / NPM namespace.
|
||||
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.
|
||||
|
||||
## First-party plugins
|
||||
- **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.
|
||||
|
||||
Here is a list of known packages that have a built-in Config Plugin.
|
||||
- **All packages here are community maintained.** They are not maintained by Expo. Feel free to improve docs, packages, tests, etc.
|
||||
|
||||
> Not all packages need a config plugin, packages that don't appear here might still work with managed EAS.
|
||||
- **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.
|
||||
|
||||
- [React Native Firebase](https://rnfirebase.io/) (`@react-native-firebase/perf`, `@react-native-firebase/app`, `@react-native-firebase/crashlytics`)
|
||||
- [react-native-google-cast](https://github.com/react-native-google-cast/react-native-google-cast)
|
||||
- [react-native-nfc-manager](https://github.com/revtel/react-native-nfc-manager)
|
||||
- [react-native-health](https://github.com/agencyenterprise/react-native-health)
|
||||
- [@react-native-mapbox-gl/maps](https://github.com/rnmapbox/maps)
|
||||
- [@logrocket/react-native](https://www.npmjs.com/package/@logrocket/react-native)
|
||||
- [sentry-expo](https://www.npmjs.com/package/sentry-expo)
|
||||
- [@leanplum/react-native-sdk](https://github.com/Leanplum/Leanplum-ReactNative-SDK#readme)
|
||||
- [react-native-fbsdk-next](https://www.npmjs.com/package/react-native-fbsdk-next)
|
||||
- [@stripe/stripe-react-native](https://www.npmjs.com/package/@stripe/stripe-react-native)
|
||||
- [@react-native-voice/voice](https://www.npmjs.com/package/@react-native-voice/voice)
|
||||
- [react-native-imglysdk](https://www.npmjs.com/package/react-native-imglysdk)
|
||||
- [@viro-community/react-viro](https://github.com/virocommunity/viro) -- [Guide](https://viro-community.readme.io/docs/integrating-with-expo)
|
||||
- [react-native-msal](https://www.npmjs.com/package/react-native-msal)
|
||||
- [react-native-spotlight-search](https://www.npmjs.com/package/react-native-spotlight-search)
|
||||
- [newrelic-react-native-agent](https://www.npmjs.com/package/newrelic-react-native-agent)
|
||||
- [react-native-videoeditorsdk](https://www.npmjs.com/package/react-native-videoeditorsdk)
|
||||
- [notifee](https://notifee.app/)
|
||||
- [App Clips](https://github.com/bndkt/react-native-app-clip/)
|
||||
- [iOS Safari Extensions](https://github.com/andrew-levy/react-native-safari-extension/)
|
||||
- [react-native-ble-plx](https://github.com/dotintent/react-native-ble-plx)
|
||||
## Looking for other config plugins?
|
||||
|
||||
### Complementary
|
||||
|
||||
More out-of-tree plugins which can be used to configure more packages.
|
||||
|
||||
- [onesignal-expo-plugin](https://github.com/OneSignal/onesignal-expo-plugin) (for use with `react-native-onesignal`)
|
||||
- Configures **iOS Notification Service Extensions** for rich push notification and custom notification actions.
|
||||
- [@lyrahealth-inc/react-native-orientation-plugin](https://www.npmjs.com/package/@lyrahealth-inc/react-native-orientation-plugin) (for use with `react-native-orientation-plugin`)
|
||||
- [airship-expo-plugin](https://www.npmjs.com/package/airship-expo-plugin) (for use with `urbanairship-react-native`)
|
||||
- [config-plugin-react-native-intercom](https://www.npmjs.com/package/config-plugin-react-native-intercom) (for use with `intercom-react-native`)
|
||||
- [@allboatsrise/expo-marketingcloudsdk](https://www.npmjs.com/package/@allboatsrise/expo-marketingcloudsdk) (for use with `react-native-marketingcloudsdk`)
|
||||
- [@ouvio/react-native-background-location-plugin](https://www.npmjs.com/package/@ouvio/react-native-background-location-plugin) (for use with `react-native-background-location-plugin`)
|
||||
- [expo-community-flipper](https://www.npmjs.com/package/expo-community-flipper) (for use with Flipper)
|
||||
- [expo-appcenter](https://www.npmjs.com/package/expo-appcenter) (for use with `appcenter`)
|
||||
- [expo-react-native-freshchat](https://www.npmjs.com/package/expo-react-native-freshchat) (for use with `react-native-freshchat-sdk`)
|
||||
- [react-native-keyevent-expo-config-plugin](https://github.com/chronsyn/react-native-keyevent-expo-config-plugin) (for use with `react-native-keyevent`)
|
||||
- [watermelondb-expo-plugin](https://github.com/morrowdigital/watermelondb-expo-plugin) (for use with `@nozbe/watermelondb`)
|
||||
|
||||
### No Plugin Required
|
||||
|
||||
Just install and rebuild! If a package doesn't require any futher setup then it most likely doesn't need an Expo config plugin. Most packages work without a config plugin (including packages not listed below):
|
||||
|
||||
- [react-native-share](https://github.com/react-native-share/react-native-share)
|
||||
- [@react-native-menu/menu](https://github.com/react-native-menu/menu)
|
||||
- [react-native-blurhash](https://github.com/mrousavy/react-native-blurhash)
|
||||
- [react-native-app-shortcuts](https://github.com/lokyoung/react-native-app-shortcuts)
|
||||
- [react-native-multiple-image-picker](https://github.com/baronha/react-native-multiple-image-picker)
|
||||
- [`@shopify/flash-list`](https://github.com/Shopify/flash-list)
|
||||
|
||||
> Feel free to [open a PR](https://github.com/expo/config-plugins/edit/main/README.md) with missing packages.
|
||||
Many React Native libraries ship their own built-in config plugins. You can find them on the [React Native Directory](https://reactnative.directory/).
|
||||
|
||||
## Contributing!
|
||||
|
||||
|
||||
+1
-3
@@ -1,3 +1 @@
|
||||
export { default } from "./src/detox/App";
|
||||
// export { default } from "./src/android-jsc-intl/App";
|
||||
// export { default } from "./src/react-native-adjust/App";
|
||||
export { default } from './src/react-native-adjust/App';
|
||||
+1
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "detoxapp",
|
||||
"name": "app",
|
||||
"slug": "app",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
@@ -29,10 +29,8 @@
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"@config-plugins/detox",
|
||||
"@config-plugins/react-native-adjust",
|
||||
"@config-plugins/react-native-callkeep",
|
||||
"@config-plugins/android-jsc-intl",
|
||||
"expo-localization"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
const iosName = "detoxapp";
|
||||
const simulator = "iPhone 16";
|
||||
const derivedDataPath = "ios/build";
|
||||
const sdk = "iphonesimulator";
|
||||
|
||||
module.exports = {
|
||||
testRunner: {
|
||||
$0: "jest",
|
||||
args: {
|
||||
config: require.resolve("./e2e/jest.config.js"),
|
||||
_: ["e2e"],
|
||||
},
|
||||
jest: {
|
||||
setupTimeout: 300000,
|
||||
reportSpecs: true,
|
||||
reportWorkerAssign: true,
|
||||
},
|
||||
},
|
||||
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: "Pixel_6_Pro_API_33",
|
||||
},
|
||||
},
|
||||
},
|
||||
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",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import { by, device, element, expect } 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 have welcome screen", async () => {
|
||||
await expect(element(by.id("welcome"))).toBeVisible();
|
||||
await captureAsync();
|
||||
});
|
||||
|
||||
xit("should show hello screen after tap", async () => {
|
||||
await element(by.id("hello_button")).tap();
|
||||
await captureAsync();
|
||||
});
|
||||
|
||||
xit("should show world screen after tap", async () => {
|
||||
await element(by.id("world_button")).tap();
|
||||
await captureAsync();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
module.exports = {
|
||||
testRunner: require.resolve("jest-circus/runner"),
|
||||
testTimeout: 120000,
|
||||
rootDir: "..",
|
||||
testMatch: ["<rootDir>/**/__tests__/*"],
|
||||
transform: {
|
||||
"^.+\\.[jt]sx?$": "ts-jest",
|
||||
},
|
||||
reporters: [require.resolve("detox/runners/jest/reporter")],
|
||||
globalSetup: "detox/runners/jest/globalSetup",
|
||||
globalTeardown: "detox/runners/jest/globalTeardown",
|
||||
testEnvironment: "detox/runners/jest/testEnvironment",
|
||||
verbose: true,
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
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) {
|
||||
return path.resolve(
|
||||
projectRoot,
|
||||
`meta/screenshots/${device.getPlatform()}/${locale}/`
|
||||
);
|
||||
}
|
||||
|
||||
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 name = `APP_${device.name}_${index}`.toUpperCase();
|
||||
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 },
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-11
@@ -1,15 +1,6 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
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";
|
||||
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,
|
||||
|
||||
+13
-29
@@ -1,42 +1,27 @@
|
||||
{
|
||||
"name": "@expo/app",
|
||||
"main": "./index.js",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"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",
|
||||
"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/android-jsc-intl": "*",
|
||||
"@config-plugins/detox": "*",
|
||||
"@config-plugins/react-native-adjust": "*",
|
||||
"@config-plugins/react-native-callkeep": "*",
|
||||
"detox": "^20.37.0",
|
||||
"expo": "~53",
|
||||
"expo-localization": "~16.1.5",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"@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",
|
||||
"i18n-js": "^3.8.0",
|
||||
"luxon": "^1.27.0",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2",
|
||||
"react-native-adjust": "^5.1.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.3.0"
|
||||
"react-native-safe-area-context": "~5.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-jsx": "^7.16.0",
|
||||
@@ -46,7 +31,6 @@
|
||||
"jest": "~29.7.0",
|
||||
"jest-circus": "^29.5.0",
|
||||
"ts-jest": "^29.0.5",
|
||||
"typescript": "~5.8.3"
|
||||
},
|
||||
"private": true
|
||||
"typescript": "~5.9.2"
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/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=${3:-"Debug"}
|
||||
# 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'
|
||||
@@ -1,28 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
@@ -1,13 +1,11 @@
|
||||
import React from "react";
|
||||
import { SafeAreaView, Text, StyleSheet } from "react-native";
|
||||
import { Adjust, AdjustConfig } from "react-native-adjust";
|
||||
import React from 'react';
|
||||
import { SafeAreaView, Text, StyleSheet } from 'react-native';
|
||||
import { Adjust, AdjustConfig } from 'react-native-adjust';
|
||||
|
||||
export default function App() {
|
||||
React.useEffect(() => {
|
||||
const adjustConfig = new AdjustConfig(
|
||||
"APP_TOKEN",
|
||||
AdjustConfig.EnvironmentSandbox
|
||||
);
|
||||
// https://github.com/adjust/react_native_sdk/blob/bd4fe59403a57599767606b2bcaa116852df973d/example/App.js#L45
|
||||
const adjustConfig = new AdjustConfig('APP_TOKEN', AdjustConfig.EnvironmentSandbox);
|
||||
Adjust.enable();
|
||||
}, []);
|
||||
|
||||
@@ -21,7 +19,7 @@ export default function App() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
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
|
||||
@@ -6,14 +6,14 @@ import withAppleSettings, {
|
||||
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";
|
||||
} 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;
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
function withWhiteLabel(config: Partial<ExpoConfig>) {
|
||||
if (!config.extra) config.extra = {};
|
||||
@@ -39,7 +39,7 @@ module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {
|
||||
// The Apple locale code. This will be used to generate the .strings file.
|
||||
en: {
|
||||
// Name of the localized key.
|
||||
Name: "Text Field",
|
||||
Name: 'Text Field',
|
||||
},
|
||||
},
|
||||
// The page object is required. It will be used to generate the .plist file.
|
||||
@@ -48,27 +48,27 @@ module.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {
|
||||
// The `PreferenceSpecifiers` defines the UI elements to generate.
|
||||
PreferenceSpecifiers: [
|
||||
Title({
|
||||
title: "Title",
|
||||
value: "Default Title",
|
||||
key: "title_preference",
|
||||
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",
|
||||
title: 'Name',
|
||||
key: 'name_preference',
|
||||
value: '',
|
||||
keyboardType: 'Alphabet',
|
||||
autoCapitalize: 'None',
|
||||
autoCorrect: 'No',
|
||||
}),
|
||||
Switch({
|
||||
title: "Enabled",
|
||||
key: "enabled_preference",
|
||||
title: 'Enabled',
|
||||
key: 'enabled_preference',
|
||||
value: true,
|
||||
}),
|
||||
Slider({
|
||||
key: "slider_preference",
|
||||
key: 'slider_preference',
|
||||
value: 0.5,
|
||||
}),
|
||||
// Broken https://forums.developer.apple.com/forums/thread/764519
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"expo": {
|
||||
"name": "apple-settings",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"platforms": ["ios"],
|
||||
"plugins": [["expo-build-properties", { "ios": { "ccacheEnabled": true } }]]
|
||||
"platforms": ["ios"]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from "./src/App";
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"name": "@config-plugins-app/apple-settings",
|
||||
"main": "./index.js",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
"android": "expo run:android",
|
||||
"e2e": "maestro test .maestro/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/apple-settings": "*",
|
||||
"@react-native-community/slider": "4.5.6",
|
||||
"@react-native-picker/picker": "2.11.0",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2"
|
||||
},
|
||||
"private": true
|
||||
"@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,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Button, Linking, StyleSheet, View } from "react-native";
|
||||
import React from 'react';
|
||||
import { Button, Linking, StyleSheet, View } from 'react-native';
|
||||
|
||||
import {
|
||||
SettingsRadioGroup,
|
||||
@@ -7,33 +7,35 @@ import {
|
||||
SettingsSwitch,
|
||||
SettingsTextInput,
|
||||
SettingsTitle,
|
||||
} from "./SettingsViews";
|
||||
} from './SettingsViews';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* <SettingsTitle settingsKey="title_preference" /> */}
|
||||
<SettingsTextInput
|
||||
testID="name-input"
|
||||
style={{
|
||||
height: 40,
|
||||
borderColor: "gray",
|
||||
borderColor: 'gray',
|
||||
borderWidth: 1,
|
||||
padding: 8,
|
||||
}}
|
||||
settingsKey="name_preference"
|
||||
/>
|
||||
<SettingsSwitch settingsKey="enabled_preference" />
|
||||
<SettingsSlider settingsKey="slider_preference" />
|
||||
<SettingsSwitch testID="enabled-switch" settingsKey="enabled_preference" />
|
||||
<SettingsSlider testID="slider" settingsKey="slider_preference" />
|
||||
<View />
|
||||
<SettingsRadioGroup settingsKey="radio_preference">
|
||||
<SettingsRadioGroup.Item label="Option 1" value="option1" />
|
||||
<SettingsRadioGroup.Item label="Option 2" value="option2" />
|
||||
</SettingsRadioGroup>
|
||||
{/* 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={() => {
|
||||
Linking.openSettings();
|
||||
void Linking.openSettings();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@@ -43,8 +45,8 @@ const App = () => {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "stretch",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'stretch',
|
||||
paddingHorizontal: 56,
|
||||
gap: 12,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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";
|
||||
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));
|
||||
@@ -32,7 +32,7 @@ export function SettingsSlider({
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof Slider>,
|
||||
"selectedValue" | "onValueChange"
|
||||
'selectedValue' | 'onValueChange'
|
||||
>) {
|
||||
const [value, setValue] = useSetting<number>(settingsKey);
|
||||
return <Slider {...props} value={value ?? 0} onValueChange={setValue} />;
|
||||
@@ -43,16 +43,10 @@ export function SettingsRadioGroup({
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof Picker>,
|
||||
"selectedValue" | "onValueChange"
|
||||
'selectedValue' | 'onValueChange'
|
||||
>) {
|
||||
const [value, setValue] = useSetting<string>(settingsKey);
|
||||
return (
|
||||
<Picker
|
||||
{...props}
|
||||
selectedValue={value ?? "option1"}
|
||||
onValueChange={setValue}
|
||||
/>
|
||||
);
|
||||
return <Picker {...props} selectedValue={value ?? 'option1'} onValueChange={setValue} />;
|
||||
}
|
||||
|
||||
SettingsRadioGroup.Item = Picker.Item;
|
||||
@@ -60,10 +54,7 @@ SettingsRadioGroup.Item = Picker.Item;
|
||||
export function SettingsSwitch({
|
||||
settingsKey,
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof Switch>,
|
||||
"value" | "onValueChange"
|
||||
>) {
|
||||
}: { settingsKey: string } & Omit<React.ComponentProps<typeof Switch>, 'value' | 'onValueChange'>) {
|
||||
const [value, setValue] = useSetting<boolean>(settingsKey);
|
||||
return <Switch {...props} value={!!value} onValueChange={setValue} />;
|
||||
}
|
||||
@@ -71,10 +62,7 @@ export function SettingsSwitch({
|
||||
export function SettingsTitle({
|
||||
settingsKey,
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof Text>,
|
||||
"children"
|
||||
>) {
|
||||
}: { settingsKey: string } & Omit<React.ComponentProps<typeof Text>, 'children'>) {
|
||||
const [value] = useSetting<string>(settingsKey);
|
||||
return <Text {...props} children={value} />;
|
||||
}
|
||||
@@ -84,7 +72,7 @@ export function SettingsTextInput({
|
||||
...props
|
||||
}: { settingsKey: string } & Omit<
|
||||
React.ComponentProps<typeof TextInput>,
|
||||
"value" | "onChangeText"
|
||||
'value' | 'onChangeText'
|
||||
>) {
|
||||
const [value, setValue] = useSetting<string>(settingsKey);
|
||||
return <TextInput {...props} value={value} onChangeText={setValue} />;
|
||||
|
||||
@@ -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": "ffmpeg",
|
||||
"icon": "https://github.com/expo.png",
|
||||
"splash": {
|
||||
"image": "https://github.com/expo.png"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"@config-plugins/ffmpeg-kit-react-native",
|
||||
{
|
||||
"package": "min"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
|
||||
import App from "./src/App";
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@config-plugins-app/ffmpeg-kit-react-native",
|
||||
"main": "./index.js",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/ffmpeg-kit-react-native": "*",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"ffmpeg-kit-react-native": "^6.0.2",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.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>
|
||||
);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
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,7 +1,7 @@
|
||||
const path = require("path");
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, "");
|
||||
const appId = "dev.bacon." + cleanName;
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"./assets/stickers/teapot.png"
|
||||
]
|
||||
}
|
||||
],
|
||||
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from "./src/App";
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,19 +1,17 @@
|
||||
{
|
||||
"name": "@config-plugins-app/ios-stickers",
|
||||
"main": "./index.js",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"prebuild": "expo prebuild -p ios --template ./expo-template-bare-minimum-50.0.17.tgz",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/ios-stickers": "*",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2"
|
||||
},
|
||||
"private": true
|
||||
"@config-plugins/ios-stickers": "workspace:*",
|
||||
"expo": "~56",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Text, View } from "react-native";
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>Open iMessage to find the app</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
const path = require("path");
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, "");
|
||||
const appId = "dev.bacon." + cleanName;
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
|
||||
@@ -5,23 +5,13 @@
|
||||
"splash": {
|
||||
"image": "https://github.com/expo.png"
|
||||
},
|
||||
"ios": {
|
||||
"config": {
|
||||
"branch": {
|
||||
"apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"config": {
|
||||
"branch": {
|
||||
"apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"@config-plugins/react-native-branch",
|
||||
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
|
||||
[
|
||||
"@config-plugins/react-native-branch",
|
||||
{
|
||||
"apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from "./src/App";
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@config-plugins-app/react-native-branch",
|
||||
"main": "./index.js",
|
||||
"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": "*",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2",
|
||||
"react-native-branch": "^6.6.0"
|
||||
},
|
||||
"private": true
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
Vendored
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { Text, View } from "react-native";
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>Branch</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
File renamed without changes.
@@ -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,23 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "app-icon",
|
||||
"icon": "./assets/icons/winter.png",
|
||||
"platforms": ["ios"],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"@config-plugins/react-native-dynamic-app-icon",
|
||||
[
|
||||
"./assets/icons/spring.png",
|
||||
"./assets/icons/summer.png",
|
||||
"./assets/icons/autumn.png",
|
||||
"./assets/icons/fall.png",
|
||||
"./assets/icons/solstice.png"
|
||||
]
|
||||
],
|
||||
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 83 KiB |
-5
@@ -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-dynamic-app-icon",
|
||||
"main": "./index.js",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@config-plugins/react-native-dynamic-app-icon": "*",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2",
|
||||
"react-native-dynamic-app-icon": "^1.1.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
const path = require("path");
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, "");
|
||||
const appId = "dev.bacon." + cleanName;
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
"splash": {
|
||||
"image": "https://github.com/expo.png"
|
||||
},
|
||||
"plugins": [
|
||||
"@config-plugins/react-native-blob-util",
|
||||
"@config-plugins/react-native-pdf",
|
||||
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
|
||||
]
|
||||
"plugins": ["@config-plugins/react-native-blob-util", "@config-plugins/react-native-pdf"]
|
||||
}
|
||||
}
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from "./src/App";
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "@config-plugins-app/react-native-pdf",
|
||||
"main": "./index.js",
|
||||
"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": "*",
|
||||
"@config-plugins/react-native-pdf": "*",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2",
|
||||
"react-native-blob-util": "^0.21.2",
|
||||
"react-native-pdf": "^6.7.7"
|
||||
},
|
||||
"private": true
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
Vendored
+5
-5
@@ -1,8 +1,8 @@
|
||||
import { SafeAreaView, StyleSheet, useWindowDimensions } from "react-native";
|
||||
import Pdf from "react-native-pdf";
|
||||
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",
|
||||
uri: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
||||
cache: true,
|
||||
};
|
||||
export default function App() {
|
||||
@@ -34,7 +34,7 @@ export default function App() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
const path = require("path");
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, "");
|
||||
const appId = "dev.bacon." + cleanName;
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
"plugins": [
|
||||
[
|
||||
"@config-plugins/react-native-siri-shortcut",
|
||||
["com.example.InitiateWorkout", "com.example.FinishWorkout"]
|
||||
],
|
||||
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
|
||||
]
|
||||
[
|
||||
"com.github.gustash.SiriShortcutsModuleExample.sayHello",
|
||||
"com.github.gustash.SiriShortcutsModuleExample.somethingElse"
|
||||
]
|
||||
]
|
||||
],
|
||||
"ios": {
|
||||
"appleTeamId": "7PSDAY43Y6"
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from "./src/App";
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@config-plugins-app/react-native-siri-shortcut",
|
||||
"main": "./index.js",
|
||||
"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": "*",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2",
|
||||
"@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"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
}
|
||||
@@ -1,264 +1,230 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
ScrollView,
|
||||
Button,
|
||||
SafeAreaView,
|
||||
View,
|
||||
EmitterSubscription,
|
||||
StyleSheet,
|
||||
Text,
|
||||
ScrollView,
|
||||
Button,
|
||||
SafeAreaView,
|
||||
View,
|
||||
EmitterSubscription,
|
||||
} from 'react-native';
|
||||
import {
|
||||
ShortcutOptions,
|
||||
ShortcutData,
|
||||
ShortcutInfo,
|
||||
donateShortcut,
|
||||
suggestShortcuts,
|
||||
clearAllShortcuts,
|
||||
clearShortcutsWithIdentifiers,
|
||||
presentShortcut,
|
||||
getShortcuts,
|
||||
addShortcutListener,
|
||||
getInitialShortcut,
|
||||
AddToSiriButton,
|
||||
SiriButtonStyles,
|
||||
ShortcutOptions,
|
||||
ShortcutData,
|
||||
ShortcutInfo,
|
||||
donateShortcut,
|
||||
suggestShortcuts,
|
||||
clearAllShortcuts,
|
||||
clearShortcutsWithIdentifiers,
|
||||
presentShortcut,
|
||||
getShortcuts,
|
||||
addShortcutListener,
|
||||
getInitialShortcut,
|
||||
AddToSiriButton,
|
||||
SiriButtonStyles,
|
||||
} from 'react-native-siri-shortcut';
|
||||
|
||||
const opts1: ShortcutOptions = {
|
||||
activityType: 'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
title: 'Say Hi',
|
||||
userInfo: {
|
||||
foo: 1,
|
||||
bar: 'baz',
|
||||
baz: 34.5,
|
||||
},
|
||||
requiredUserInfoKeys: ['foo', 'bar', 'baz'],
|
||||
keywords: ['kek', 'foo', 'bar'],
|
||||
persistentIdentifier:
|
||||
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
isEligibleForSearch: true,
|
||||
isEligibleForPrediction: true,
|
||||
suggestedInvocationPhrase: 'Say something',
|
||||
needsSave: true,
|
||||
activityType: 'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
title: 'Say Hi',
|
||||
userInfo: {
|
||||
foo: 1,
|
||||
bar: 'baz',
|
||||
baz: 34.5,
|
||||
},
|
||||
requiredUserInfoKeys: ['foo', 'bar', 'baz'],
|
||||
keywords: ['kek', 'foo', 'bar'],
|
||||
persistentIdentifier: 'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
isEligibleForSearch: true,
|
||||
isEligibleForPrediction: true,
|
||||
suggestedInvocationPhrase: 'Say something',
|
||||
needsSave: true,
|
||||
};
|
||||
|
||||
const opts2: ShortcutOptions = {
|
||||
activityType: 'com.github.gustash.SiriShortcutsModuleExample.somethingElse',
|
||||
title: 'Something Else',
|
||||
persistentIdentifier: 'some.persistent.identifier',
|
||||
isEligibleForSearch: true,
|
||||
isEligibleForPrediction: true,
|
||||
suggestedInvocationPhrase: "What's up?",
|
||||
description: 'Just a random description',
|
||||
activityType: 'com.github.gustash.SiriShortcutsModuleExample.somethingElse',
|
||||
title: 'Something Else',
|
||||
persistentIdentifier: 'some.persistent.identifier',
|
||||
isEligibleForSearch: true,
|
||||
isEligibleForPrediction: true,
|
||||
suggestedInvocationPhrase: "What's up?",
|
||||
description: 'Just a random description',
|
||||
};
|
||||
type State = {
|
||||
shortcutInfo: any | null,
|
||||
shortcutActivityType: string | null,
|
||||
addToSiriStyle: 0 | 1 | 2 | 3 | 4 | 5,
|
||||
shortcuts: Array<ShortcutData>,
|
||||
shortcutInfo: any | null;
|
||||
shortcutActivityType: string | null;
|
||||
addToSiriStyle: 0 | 1 | 2 | 3 | 4 | 5;
|
||||
shortcuts: Array<ShortcutData>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default class App extends Component<void, State> {
|
||||
listener: EmitterSubscription | null = null;
|
||||
listener: EmitterSubscription | null = null;
|
||||
|
||||
state: State = {
|
||||
shortcutInfo: null,
|
||||
shortcutActivityType: null,
|
||||
addToSiriStyle: SiriButtonStyles.blackOutline,
|
||||
shortcuts: [],
|
||||
};
|
||||
state: State = {
|
||||
shortcutInfo: null,
|
||||
shortcutActivityType: null,
|
||||
addToSiriStyle: SiriButtonStyles.blackOutline,
|
||||
shortcuts: [],
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.handleInitialShortcut();
|
||||
this.listener = addShortcutListener(this.handleSiriShortcut.bind(this));
|
||||
componentDidMount() {
|
||||
this.handleInitialShortcut();
|
||||
this.listener = addShortcutListener(this.handleSiriShortcut.bind(this));
|
||||
|
||||
// This will suggest these two shortcuts so that they appear
|
||||
// in Settings > Siri & Search, even if they are not yet
|
||||
// donated. Suitable for shortcuts that you expect the user
|
||||
// may want to use. (https://developer.apple.com/documentation/sirikit/shortcut_management/suggesting_shortcuts_to_users)
|
||||
suggestShortcuts([opts1, opts2]);
|
||||
// This will suggest these two shortcuts so that they appear
|
||||
// in Settings > Siri & Search, even if they are not yet
|
||||
// donated. Suitable for shortcuts that you expect the user
|
||||
// may want to use. (https://developer.apple.com/documentation/sirikit/shortcut_management/suggesting_shortcuts_to_users)
|
||||
suggestShortcuts([opts1, opts2]);
|
||||
|
||||
this.updateShortcutList();
|
||||
this.updateShortcutList();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.listener) {
|
||||
this.listener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
async handleInitialShortcut() {
|
||||
const initialShortcut = await getInitialShortcut();
|
||||
|
||||
if (!initialShortcut) {
|
||||
return;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.listener) {
|
||||
this.listener.remove();
|
||||
}
|
||||
this.handleSiriShortcut(initialShortcut);
|
||||
}
|
||||
|
||||
handleSiriShortcut({ userInfo, activityType }: ShortcutInfo) {
|
||||
this.setState({
|
||||
shortcutInfo: userInfo,
|
||||
shortcutActivityType: activityType,
|
||||
});
|
||||
}
|
||||
|
||||
setupShortcut1() {
|
||||
donateShortcut(opts1);
|
||||
}
|
||||
|
||||
setupShortcut2() {
|
||||
donateShortcut(opts2);
|
||||
}
|
||||
|
||||
async clearShortcut1() {
|
||||
try {
|
||||
await clearShortcutsWithIdentifiers([
|
||||
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
]);
|
||||
alert('Cleared Shortcut 1');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
async handleInitialShortcut() {
|
||||
const initialShortcut = await getInitialShortcut();
|
||||
|
||||
if (!initialShortcut) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleSiriShortcut(initialShortcut);
|
||||
async clearShortcut2() {
|
||||
try {
|
||||
await clearShortcutsWithIdentifiers(['some.persistent.identifier']);
|
||||
alert('Cleared Shortcut 2');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
handleSiriShortcut({ userInfo, activityType }: ShortcutInfo) {
|
||||
this.setState({
|
||||
shortcutInfo: userInfo,
|
||||
shortcutActivityType: activityType,
|
||||
});
|
||||
async updateShortcutList() {
|
||||
try {
|
||||
const shortcuts = await getShortcuts();
|
||||
|
||||
this.setState({
|
||||
shortcuts,
|
||||
});
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
setupShortcut1() {
|
||||
donateShortcut(opts1);
|
||||
async clearBothShortcuts() {
|
||||
try {
|
||||
await clearShortcutsWithIdentifiers([
|
||||
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
'some.persistent.identifier',
|
||||
]);
|
||||
alert('Cleared Both Shortcuts');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
setupShortcut2() {
|
||||
donateShortcut(opts2);
|
||||
async clearShortcuts() {
|
||||
try {
|
||||
await clearAllShortcuts();
|
||||
alert('Deleted all the shortcuts');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
async clearShortcut1() {
|
||||
try {
|
||||
await clearShortcutsWithIdentifiers([
|
||||
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
]);
|
||||
alert('Cleared Shortcut 1');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
swapSiriButtonTheme() {
|
||||
const { addToSiriStyle } = this.state;
|
||||
|
||||
async clearShortcut2() {
|
||||
try {
|
||||
await clearShortcutsWithIdentifiers(['some.persistent.identifier']);
|
||||
alert('Cleared Shortcut 2');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
const styles = Object.keys(SiriButtonStyles).map((key) => SiriButtonStyles[key]);
|
||||
const index = styles.findIndex((style) => style === addToSiriStyle);
|
||||
if (index === styles.length - 1) this.setState({ addToSiriStyle: styles[0] });
|
||||
else this.setState({ addToSiriStyle: styles[index + 1] });
|
||||
}
|
||||
|
||||
async updateShortcutList() {
|
||||
try {
|
||||
const shortcuts = await getShortcuts();
|
||||
render() {
|
||||
const { shortcutInfo, shortcutActivityType, addToSiriStyle, shortcuts } = this.state;
|
||||
|
||||
this.setState({
|
||||
shortcuts,
|
||||
});
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
async clearBothShortcuts() {
|
||||
try {
|
||||
await clearShortcutsWithIdentifiers([
|
||||
'com.github.gustash.SiriShortcutsModuleExample.sayHello',
|
||||
'some.persistent.identifier',
|
||||
]);
|
||||
alert('Cleared Both Shortcuts');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
async clearShortcuts() {
|
||||
try {
|
||||
await clearAllShortcuts();
|
||||
alert('Deleted all the shortcuts');
|
||||
} catch (e) {
|
||||
alert("You're not running iOS 12!");
|
||||
}
|
||||
}
|
||||
|
||||
swapSiriButtonTheme() {
|
||||
const { addToSiriStyle } = this.state;
|
||||
|
||||
const styles = Object.keys(SiriButtonStyles).map(
|
||||
key => SiriButtonStyles[key],
|
||||
);
|
||||
const index = styles.findIndex(style => style === addToSiriStyle);
|
||||
if (index === styles.length - 1) this.setState({ addToSiriStyle: styles[0] });
|
||||
else this.setState({ addToSiriStyle: styles[index + 1] });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { shortcutInfo, shortcutActivityType, addToSiriStyle, shortcuts } =
|
||||
this.state;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={styles.contentContainer}>
|
||||
<Text>Shortcut Activity Type: {shortcutActivityType || 'None'}</Text>
|
||||
<Text>
|
||||
Shortcut Info:{' '}
|
||||
{shortcutInfo ? JSON.stringify(shortcutInfo) : 'No shortcut data.'}
|
||||
</Text>
|
||||
<Button
|
||||
title="Create Shortcut 1"
|
||||
onPress={this.setupShortcut1.bind(this)}
|
||||
/>
|
||||
<Button
|
||||
title="Create Shortcut 2"
|
||||
onPress={this.setupShortcut2.bind(this)}
|
||||
/>
|
||||
<Button
|
||||
title="Clear Shortcut 1"
|
||||
onPress={this.clearShortcut1.bind(this)}
|
||||
/>
|
||||
<Button
|
||||
title="Clear Shortcut 2"
|
||||
onPress={this.clearShortcut2.bind(this)}
|
||||
/>
|
||||
<Button
|
||||
title="Clear Both Shortcuts"
|
||||
onPress={this.clearBothShortcuts.bind(this)}
|
||||
/>
|
||||
<Button
|
||||
title="Delete All Shortcuts"
|
||||
onPress={this.clearShortcuts.bind(this)}
|
||||
/>
|
||||
<Button
|
||||
title="Update list of shortcuts"
|
||||
onPress={this.updateShortcutList.bind(this)}
|
||||
/>
|
||||
<AddToSiriButton
|
||||
buttonStyle={addToSiriStyle}
|
||||
onPress={() => {
|
||||
presentShortcut(opts1, ({ status }) => {
|
||||
console.log(`I was ${status}`);
|
||||
});
|
||||
}}
|
||||
shortcut={opts1}
|
||||
/>
|
||||
<Button
|
||||
title="Swap Siri Button Theme"
|
||||
onPress={this.swapSiriButtonTheme.bind(this)}
|
||||
/>
|
||||
{shortcuts.length ? (
|
||||
shortcuts.map(({ identifier, phrase, options }, i) => (
|
||||
<View key={identifier}>
|
||||
<Text>Shortcut {i + 1}:</Text>
|
||||
<Text>Identifier - {identifier}</Text>
|
||||
<Text>Phrase - {phrase}</Text>
|
||||
<Text>Options - {JSON.stringify(options)}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<Text>No Shortcuts yet</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
|
||||
<Text>Shortcut Activity Type: {shortcutActivityType || 'None'}</Text>
|
||||
<Text>
|
||||
Shortcut Info: {shortcutInfo ? JSON.stringify(shortcutInfo) : 'No shortcut data.'}
|
||||
</Text>
|
||||
<Button title="Create Shortcut 1" onPress={this.setupShortcut1.bind(this)} />
|
||||
<Button title="Create Shortcut 2" onPress={this.setupShortcut2.bind(this)} />
|
||||
<Button title="Clear Shortcut 1" onPress={this.clearShortcut1.bind(this)} />
|
||||
<Button title="Clear Shortcut 2" onPress={this.clearShortcut2.bind(this)} />
|
||||
<Button title="Clear Both Shortcuts" onPress={this.clearBothShortcuts.bind(this)} />
|
||||
<Button title="Delete All Shortcuts" onPress={this.clearShortcuts.bind(this)} />
|
||||
<Button title="Update list of shortcuts" onPress={this.updateShortcutList.bind(this)} />
|
||||
<AddToSiriButton
|
||||
buttonStyle={addToSiriStyle}
|
||||
onPress={() => {
|
||||
presentShortcut(opts1, ({ status }) => {
|
||||
console.log(`I was ${status}`);
|
||||
});
|
||||
}}
|
||||
shortcut={opts1}
|
||||
/>
|
||||
<Button title="Swap Siri Button Theme" onPress={this.swapSiriButtonTheme.bind(this)} />
|
||||
{shortcuts.length ? (
|
||||
shortcuts.map(({ identifier, phrase, options }, i) => (
|
||||
<View key={identifier}>
|
||||
<Text>Shortcut {i + 1}:</Text>
|
||||
<Text>Identifier - {identifier}</Text>
|
||||
<Text>Phrase - {phrase}</Text>
|
||||
<Text>Options - {JSON.stringify(options)}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<Text>No Shortcuts yet</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
contentContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
contentContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { View, Text } from "react-native";
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>No-op on this platform</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const path = require("path");
|
||||
const path = require('path');
|
||||
const folderName = path.basename(__dirname);
|
||||
const cleanName = folderName.replace(/[_\s-]/g, "");
|
||||
const appId = "dev.bacon." + cleanName;
|
||||
const cleanName = folderName.replace(/[_\s-]/g, '');
|
||||
const appId = 'dev.bacon.' + cleanName;
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (!config.extra) config.extra = {};
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"splash": {
|
||||
"image": "https://github.com/expo.png"
|
||||
},
|
||||
"plugins": [
|
||||
"@config-plugins/react-native-webrtc",
|
||||
["expo-build-properties", { "ios": { "ccacheEnabled": true } }]
|
||||
]
|
||||
"plugins": ["@config-plugins/react-native-webrtc"]
|
||||
}
|
||||
}
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { registerRootComponent } from "expo";
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from "./src/App";
|
||||
import App from './src/App';
|
||||
|
||||
registerRootComponent(App);
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@config-plugins-app/react-native-webrtc",
|
||||
"main": "./index.js",
|
||||
"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-webrtc": "*",
|
||||
"expo": "~53",
|
||||
"expo-splash-screen": "~0.30.7",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.2",
|
||||
"react-native-webrtc": "^124.0.5"
|
||||
},
|
||||
"private": true
|
||||
"@config-plugins/react-native-webrtc": "workspace:*",
|
||||
"expo": "~56",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-webrtc": "^124.0.7"
|
||||
}
|
||||
}
|
||||
Vendored
+6
-12
@@ -1,17 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
SafeAreaView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { mediaDevices, RTCView } from "react-native-webrtc";
|
||||
import { 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");
|
||||
console.log('start');
|
||||
if (!stream) {
|
||||
let s;
|
||||
try {
|
||||
@@ -23,7 +17,7 @@ const App = () => {
|
||||
}
|
||||
};
|
||||
const stop = () => {
|
||||
console.log("stop");
|
||||
console.log('stop');
|
||||
if (stream) {
|
||||
stream.release();
|
||||
setStream(null);
|
||||
@@ -51,7 +45,7 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
footer: {
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Shared Jest configuration for every package under packages/.
|
||||
// Packages require this file from their own jest.config.js.
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
testRegex: '/__tests__/.*(test|spec)\\.[jt]sx?$',
|
||||
transform: {
|
||||
'^.+\\.[jt]sx?$': [
|
||||
require.resolve('@swc/jest'),
|
||||
{
|
||||
jsc: {
|
||||
parser: { syntax: 'typescript', dynamicImport: true },
|
||||
target: 'es2022',
|
||||
},
|
||||
module: { type: 'commonjs' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// Jest 29 cannot format inline snapshots with prettier 3. Skip formatting
|
||||
// instead of pinning a second prettier.
|
||||
prettierPath: null,
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"useWorkspaces": true,
|
||||
"version": "independent",
|
||||
"packages": ["packages/*"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { defineConfig } from 'oxlint';
|
||||
import node from 'oxlint-config-universe/node';
|
||||
|
||||
// Shared oxlint configuration for every package under packages/ and for scripts/.
|
||||
// Derived from the base config in expo/expo's @expo/internal-scripts, trimmed to
|
||||
// the rules that apply to plain Node TypeScript.
|
||||
export default defineConfig({
|
||||
extends: [node],
|
||||
// NOTE: oxlint does not inherit `ignorePatterns` through `extends`, so packages
|
||||
// re-export this whole config instead of extending it.
|
||||
ignorePatterns: ['**/node_modules/**', '**/build/**', '**/.expo/**'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/__tests__/**', '**/__mocks__/**', '**/jest/**'],
|
||||
env: { jest: true, node: true },
|
||||
rules: {
|
||||
// Tests need to re-assign imported bindings to install mocks and spies.
|
||||
'no-import-assign': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.config.js', '*.config.mjs', '.*rc.js'],
|
||||
env: { node: true },
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.d.ts'],
|
||||
rules: {
|
||||
// Handled by TypeScript.
|
||||
'no-unused-expressions': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'no-useless-return': 'off',
|
||||
// Enabling it blocks legitimate type overloads, which TypeScript already validates.
|
||||
'no-dupe-class-members': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
// Handled by TypeScript.
|
||||
'no-unsafe-optional-chaining': 'off',
|
||||
// Kept off to match expo/expo, where it was suppressed by eslint-config-prettier.
|
||||
curly: 'off',
|
||||
},
|
||||
});
|
||||
+28
-21
@@ -3,38 +3,45 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"postinstall": "expo-yarn-workspaces check-workspace-dependencies",
|
||||
"gen": "ts-node ./scripts/generate-plugin.ts",
|
||||
"update-issue-template": "ts-node ./scripts/gh-issues-config.ts",
|
||||
"update-dependabot-config": "ts-node ./scripts/update-dependabot-config.ts",
|
||||
"start": "lerna run build --parallel -- --watch",
|
||||
"prepare": "lerna run prepare --parallel"
|
||||
},
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"resolutions": {
|
||||
"expo": "^53"
|
||||
"gen": "tsx ./scripts/generate-plugin.ts",
|
||||
"update-issue-template": "tsx ./scripts/gh-issues-config.ts",
|
||||
"update-dependabot-config": "tsx ./scripts/update-dependabot-config.ts",
|
||||
"start": "pnpm --filter './packages/*' --parallel run build -- --watch",
|
||||
"bump": "pnpm --filter './packages/*' -r exec pnpm version patch --no-git-tag-version",
|
||||
"release": "pnpm publish -r --filter './packages/*'",
|
||||
"prepare": "pnpm --filter './packages/*' --parallel run prepare",
|
||||
"format": "oxfmt",
|
||||
"lint": "oxlint --config oxlint.config.mjs scripts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@expo/package-manager": "^1.8.4",
|
||||
"@swc/core": "^1.16.18",
|
||||
"@swc/jest": "^0.2.39",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/prompts": "^2.0.13",
|
||||
"@types/react": "~18.3.12",
|
||||
"eslint": "^8.10.0",
|
||||
"expo-module-scripts": "^4.1.6",
|
||||
"expo-yarn-workspaces": "^2.3.2",
|
||||
"@types/react": "^19.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"memfs": "^3.4.4",
|
||||
"lerna": "3.4.1",
|
||||
"prettier": "^3",
|
||||
"oxfmt": "^0.73.0",
|
||||
"oxlint": "^1.88.0",
|
||||
"oxlint-config-universe": "^0.2.0",
|
||||
"prompts": "^2.4.1",
|
||||
"remark-cli": "^9.0.0",
|
||||
"remark-validate-links": "^10.0.2",
|
||||
"string.prototype.replaceall": "^1.0.5",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"devEngines": {
|
||||
"packageManager": {
|
||||
"name": "pnpm",
|
||||
"version": "12.2.1",
|
||||
"onFail": "warn"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": "^12.2.1"
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// @generated by expo-module-scripts
|
||||
module.exports = require('expo-module-scripts/eslintrc.base.js');
|
||||
@@ -1,56 +0,0 @@
|
||||
### @config-plugins/android-jsc-intl 5.0.0
|
||||
|
||||
- Expo SDK 48 support
|
||||
|
||||
## @config-plugins/android-jsc-intl [2.0.0](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-05-10)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
- **android-jsc-intl:** add SDK 45 support (#77)
|
||||
|
||||
### Features
|
||||
|
||||
- **android-jsc-intl:** add SDK 45 support ([#77](https://github.com/expo/config-plugins/issues/77)) ([aed0904](https://github.com/expo/config-plugins/commit/aed09045d61bc67b375a277b96bdc90ed5245d0f))
|
||||
|
||||
### @config-plugins/android-jsc-intl [1.0.4](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-23)
|
||||
|
||||
### Other chores
|
||||
|
||||
- **android-jsc-intl:** lint fix ([5ca4e44](https://github.com/expo/config-plugins/commit/5ca4e44a49ad5c3153b4a2b15acf69db594d76b8))
|
||||
|
||||
### @config-plugins/android-jsc-intl [1.0.3](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-22)
|
||||
|
||||
### Other chores
|
||||
|
||||
- git ignore build folders ([#59](https://github.com/expo/config-plugins/issues/59)) ([d6050be](https://github.com/expo/config-plugins/commit/d6050beb2a5c68dc59287c27ec388c2002ec7904))
|
||||
|
||||
### @config-plugins/android-jsc-intl [1.0.2](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-16)
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
- fix versions [skip ci] ([#53](https://github.com/expo/config-plugins/issues/53)) ([5de4ae3](https://github.com/expo/config-plugins/commit/5de4ae3e6182c32b7aa24d70ccd23a11663bb089))
|
||||
|
||||
### @config-plugins/android-jsc-intl [1.0.1](https://github.com/expo/config-plugins/compare/@config-plugins/[email protected]...@config-plugins/[email protected]) (2022-03-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **ci:** use correct internal versions ([ab8076a](https://github.com/expo/config-plugins/commit/ab8076adf51e9ba0439eba60b153d729a0996b8d))
|
||||
|
||||
## @config-plugins/android-jsc-intl 1.0.0 (2022-03-15)
|
||||
|
||||
### Features
|
||||
|
||||
- Intl support for android via withAndroidJscIntl ([eb6642c](https://github.com/expo/config-plugins/commit/eb6642c0e7b37d6d9baf196ec40cc520f559e665))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- remove createPluginOnce ([68cf1c0](https://github.com/expo/config-plugins/commit/68cf1c07b2c48249cbbd0732668ea95455636a0b))
|
||||
- run expo prepare ([8d364ed](https://github.com/expo/config-plugins/commit/8d364ed99ea4b0e0e3af73dbcc780819bf55d6c2))
|
||||
|
||||
### Other chores
|
||||
|
||||
- upgrade @expo/config-plugins ([#49](https://github.com/expo/config-plugins/issues/49)) ([119e31e](https://github.com/expo/config-plugins/commit/119e31edf110409272ace750f02d651124e1a22d))
|
||||
|
||||
### Documentation changes
|
||||
|
||||
- clarify readme ([b524633](https://github.com/expo/config-plugins/commit/b5246338cb37ae41d138bc6253703b0d438fc3e3))
|
||||
@@ -1,25 +1,4 @@
|
||||
# @config-plugins/android-jsc-intl
|
||||
|
||||
Expo Config Plugin to auto-configure Android JSC Intl when the native code is generated (`npx expo prebuild`).
|
||||
|
||||
Adding this plugin lets you use `Intl` in your Android app, without using Hermes.
|
||||
|
||||
## Expo installation
|
||||
|
||||
> This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.io/workflow/customizing/).
|
||||
|
||||
First install the package with yarn, npm, or [`npx expo install`](https://docs.expo.io/workflow/expo-cli/#expo-install).
|
||||
|
||||
```sh
|
||||
npx expo install @config-plugins/android-jsc-intl
|
||||
```
|
||||
|
||||
After installing this npm package, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your `app.json` or `app.config.js`:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": ["@config-plugins/android-jsc-intl"]
|
||||
}
|
||||
```
|
||||
|
||||
Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) guide.
|
||||
> [!IMPORTANT]
|
||||
> React Native uses the Hermes engine by default. If you're looking for a replacement, see the config plugin in [`react-native-community/jsc-android-buildscripts`](https://github.com/react-native-community/jsc-android-buildscripts/blob/dbbf3a7d2c23b25bc37af9ada1674eeb94cf83a9/test/plugins/withJscAndroid.js).
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require("./build/withAndroidJscIntl");
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "@config-plugins/android-jsc-intl",
|
||||
"version": "11.0.0",
|
||||
"description": "Config plugin for android-jsc-intl package",
|
||||
"main": "build/withAndroidJscIntl.js",
|
||||
"types": "build/withAndroidJscIntl.d.ts",
|
||||
"sideEffects": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/expo/config-plugins.git",
|
||||
"directory": "packages/android-jsc-intl"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "expo-module build",
|
||||
"clean": "expo-module clean",
|
||||
"lint": "expo-module lint",
|
||||
"test": "expo-module test",
|
||||
"prepare": "expo-module prepare",
|
||||
"prepublishOnly": "expo-module prepublishOnly",
|
||||
"expo-module": "expo-module"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"expo",
|
||||
"config-plugins",
|
||||
"prebuild",
|
||||
"android-jsc-intl"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"expo": "^53"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { type ConfigPlugin, withAppBuildGradle } from "expo/config-plugins";
|
||||
|
||||
/**
|
||||
* Apply android-jsc-intl native configuration.
|
||||
*
|
||||
* This plugin lets you access the `Intl` API in Android apps (without Hermes).
|
||||
*/
|
||||
const withAndroidJscIntl: ConfigPlugin = (config) =>
|
||||
withAppBuildGradle(config, (config) => {
|
||||
if (config.modResults.language !== "groovy") {
|
||||
throw new Error(
|
||||
"[@expo/config-plugins][withAndroidJscIntl] Cannot enable Intl in Android JSC app gradle because the build.gradle is not groovy.",
|
||||
);
|
||||
}
|
||||
|
||||
config.modResults.contents = config.modResults.contents.replace(
|
||||
"org.webkit:android-jsc:+",
|
||||
"org.webkit:android-jsc-intl:+",
|
||||
);
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
export default withAndroidJscIntl;
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "expo-module-scripts/tsconfig.plugin",
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"rootDir": "src",
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": ["./src"],
|
||||
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// @generated by expo-module-scripts
|
||||
module.exports = require('expo-module-scripts/eslintrc.base.js');
|
||||
@@ -10,7 +10,10 @@ Ensure you use versions that work together!
|
||||
|
||||
| `expo` | `@config-plugins/apple-settings` |
|
||||
| ------ | -------------------------------- |
|
||||
| 53.0.0 | ^4.0.0 |
|
||||
| 56.0.0 | ^8.0.0 |
|
||||
| 55.0.0 | ^7.0.0 |
|
||||
| 54.0.0 | ^6.0.0 |
|
||||
| 53.0.0 | ^5.0.0 |
|
||||
| 52.0.0 | ^3.0.0 |
|
||||
| 51.0.0 | ^2.0.0 |
|
||||
| 50.0.0 | ^1.0.0 |
|
||||
@@ -40,7 +43,7 @@ const {
|
||||
Slider,
|
||||
ChildPane,
|
||||
Group,
|
||||
} = require("@config-plugins/apple-settings");
|
||||
} = require('@config-plugins/apple-settings');
|
||||
|
||||
module.exports = (config) => {
|
||||
return withAppleSettings(config, {
|
||||
@@ -51,7 +54,7 @@ module.exports = (config) => {
|
||||
// The Apple locale code. This will be used to generate the .strings file.
|
||||
en: {
|
||||
// Name of the localized key.
|
||||
Name: "Text Field",
|
||||
Name: 'Text Field',
|
||||
},
|
||||
},
|
||||
// The page object is required. It will be used to generate the .plist file.
|
||||
@@ -62,25 +65,25 @@ module.exports = (config) => {
|
||||
// Helper models can be used to generate the UI elements using a syntax that's
|
||||
// similar to React Native.
|
||||
TextField({
|
||||
title: "Name",
|
||||
key: "name_preference",
|
||||
value: "",
|
||||
keyboardType: "Alphabet",
|
||||
autoCapitalize: "None",
|
||||
autoCorrect: "No",
|
||||
title: 'Name',
|
||||
key: 'name_preference',
|
||||
value: '',
|
||||
keyboardType: 'Alphabet',
|
||||
autoCapitalize: 'None',
|
||||
autoCorrect: 'No',
|
||||
}),
|
||||
Switch({
|
||||
title: "Enabled",
|
||||
key: "enabled_preference",
|
||||
title: 'Enabled',
|
||||
key: 'enabled_preference',
|
||||
value: true,
|
||||
}),
|
||||
Slider({
|
||||
key: "slider_preference",
|
||||
key: 'slider_preference',
|
||||
value: 0.5,
|
||||
}),
|
||||
// Child panes can be used to create nested pages.
|
||||
ChildPane({
|
||||
title: "About",
|
||||
title: 'About',
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -89,7 +92,7 @@ module.exports = (config) => {
|
||||
page: {
|
||||
PreferenceSpecifiers: [
|
||||
Group({
|
||||
title: "About Info",
|
||||
title: 'About Info',
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -114,7 +117,7 @@ const {
|
||||
// Group,
|
||||
// MultiValue,
|
||||
// RadioGroup,
|
||||
} = require("@config-plugins/apple-settings");
|
||||
} = require('@config-plugins/apple-settings');
|
||||
```
|
||||
|
||||
## Types
|
||||
@@ -140,7 +143,7 @@ import {
|
||||
AnyPreferenceSpecifier,
|
||||
UserInterfaceIdiom,
|
||||
ScalarType,
|
||||
} from "@config-plugins/apple-settings";
|
||||
} from '@config-plugins/apple-settings';
|
||||
```
|
||||
|
||||
## Dynamic usage
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const generateImageAsync = jest.fn(async (input: any, { src }) => {
|
||||
const fs = require("fs");
|
||||
const fs = require('fs');
|
||||
return { source: fs.readFileSync(src) };
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const path = require("path");
|
||||
const path = require('path');
|
||||
|
||||
const roots = ["__mocks__", "src"];
|
||||
const roots = ['__mocks__', 'src'];
|
||||
|
||||
module.exports = {
|
||||
...require("expo-module-scripts/jest-preset-plugin"),
|
||||
...require('../../jest.config.base.js'),
|
||||
rootDir: path.resolve(__dirname),
|
||||
displayName: require("./package").name,
|
||||
displayName: require('./package').name,
|
||||
roots,
|
||||
setupFiles: ["<rootDir>/jest/setup.js"],
|
||||
setupFiles: ['<rootDir>/jest/setup.js'],
|
||||
clearMocks: true,
|
||||
};
|
||||
@@ -1,2 +1,2 @@
|
||||
jest.mock("@expo/image-utils");
|
||||
jest.mock("fs");
|
||||
jest.mock('@expo/image-utils');
|
||||
jest.mock('fs');
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from '../../oxlint.config.mjs';
|
||||
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"name": "@config-plugins/apple-settings",
|
||||
"version": "5.0.0",
|
||||
"version": "8.0.3",
|
||||
"description": "Config plugin to generate custom Apple settings UI",
|
||||
"license": "MIT",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"build"
|
||||
"keywords": [
|
||||
"expo",
|
||||
"react"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/expo/config-plugins.git",
|
||||
"directory": "packages/apple-settings"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "expo-module build",
|
||||
"clean": "expo-module clean",
|
||||
"lint": "expo-module lint",
|
||||
"test": "expo-module test",
|
||||
"generate-types": "json2ts -i ./src/schema/SettingsPlist.json -o ./src/schema/SettingsPlist.ts --additionalProperties=false --unknownAny=false",
|
||||
"prepare": "expo-module prepare && yarn generate-types",
|
||||
"prepublishOnly": "expo-module prepublishOnly",
|
||||
"expo-module": "expo-module"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"expo"
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf build",
|
||||
"lint": "oxlint --config oxlint.config.mjs .",
|
||||
"test": "jest",
|
||||
"generate-types": "json2ts -i ./src/schema/SettingsPlist.json -o ./src/schema/SettingsPlist.ts --additionalProperties=false --unknownAny=false",
|
||||
"prepare": "pnpm generate-types",
|
||||
"prepublishOnly": "pnpm generate-types && pnpm run clean && pnpm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bacons/xcode": "^1.0.0-alpha.12",
|
||||
"@expo/plist": "^0.3.4",
|
||||
"@expo/plist": "catalog:",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"schema-utils": "^4.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "^53"
|
||||
"schema-utils": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"json-schema-to-typescript": "^13.0.2"
|
||||
"jest": "catalog:",
|
||||
"json-schema-to-typescript": "^16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
// Ports https://github.com/iteufel/node-strings-file/blob/master/index.js to TyeScript and ESM:
|
||||
|
||||
import fs from "fs";
|
||||
import iconv from "iconv-lite";
|
||||
import fs from 'fs';
|
||||
import iconv from 'iconv-lite';
|
||||
|
||||
type ParsedStrings = {
|
||||
[key: string]: string | { value: string; comment: string };
|
||||
};
|
||||
|
||||
export function parse(data: string, wantComments?: boolean): ParsedStrings {
|
||||
if (data.indexOf("\n") === -1) {
|
||||
data += "\n";
|
||||
if (data.indexOf('\n') === -1) {
|
||||
data += '\n';
|
||||
}
|
||||
const re = /(?:\/\*(.+)\*\/\n)?(.+)\s*=\s*"(.+)";\n/gim;
|
||||
const res: ParsedStrings = {};
|
||||
@@ -19,30 +19,32 @@ export function parse(data: string, wantComments?: boolean): ParsedStrings {
|
||||
if (m.index === re.lastIndex) {
|
||||
re.lastIndex++;
|
||||
}
|
||||
if (m[2].substring(0, 1) === '"') {
|
||||
m[2] = m[2].trim().slice(1, -1);
|
||||
let key = m[2]!;
|
||||
const value = m[3]!;
|
||||
if (key.substring(0, 1) === '"') {
|
||||
key = key.trim().slice(1, -1);
|
||||
}
|
||||
if (wantComments) {
|
||||
res[m[2]] = {
|
||||
value: unescapeString(m[3]),
|
||||
comment: m[1] || "",
|
||||
res[key] = {
|
||||
value: unescapeString(value),
|
||||
comment: m[1] || '',
|
||||
};
|
||||
} else {
|
||||
res[m[2]] = unescapeString(m[3]);
|
||||
res[key] = unescapeString(value);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function build(obj: ParsedStrings): string {
|
||||
let data = "";
|
||||
let data = '';
|
||||
for (const i in obj) {
|
||||
if (typeof obj[i] === "object") {
|
||||
if (obj[i]["comment"] && obj[i]["comment"].length > 0) {
|
||||
data += `\n/*${obj[i]["comment"]}*/\n`;
|
||||
if (typeof obj[i] === 'object') {
|
||||
if (obj[i]['comment'] && obj[i]['comment'].length > 0) {
|
||||
data += `\n/*${obj[i]['comment']}*/\n`;
|
||||
}
|
||||
data += `"${i}" = "${escapeString(obj[i]["value"])}";\n`;
|
||||
} else if (typeof obj[i] === "string") {
|
||||
data += `"${i}" = "${escapeString(obj[i]['value'])}";\n`;
|
||||
} else if (typeof obj[i] === 'string') {
|
||||
data += `\n"${i}" = "${escapeString(obj[i])}";\n`;
|
||||
}
|
||||
}
|
||||
@@ -59,13 +61,9 @@ function unescapeString(str: string): string {
|
||||
|
||||
export async function openAsync(file: string, wantComments?: boolean) {
|
||||
const data = await fs.promises.readFile(file);
|
||||
return parse(iconv.decode(data, "utf-16"), wantComments);
|
||||
return parse(iconv.decode(data, 'utf-16'), wantComments);
|
||||
}
|
||||
|
||||
export function writeAsync(filename: string, data: ParsedStrings) {
|
||||
return fs.promises.writeFile(
|
||||
filename,
|
||||
iconv.encode(build(data), "utf-16"),
|
||||
"binary",
|
||||
);
|
||||
return fs.promises.writeFile(filename, iconv.encode(build(data), 'utf-16'), 'binary');
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
import * as plist from "@expo/plist";
|
||||
import * as plist from '@expo/plist';
|
||||
import {
|
||||
BaseMods,
|
||||
type ConfigPlugin,
|
||||
createRunOncePlugin,
|
||||
type Mod,
|
||||
withMod,
|
||||
} from "expo/config-plugins";
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
import { validate } from "schema-utils";
|
||||
} from 'expo/config-plugins';
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import { validate } from 'schema-utils';
|
||||
|
||||
import { SettingsPlist } from "../schema/SettingsPlist";
|
||||
import schema from "../schema/SettingsPlist.json";
|
||||
import { SettingsPlist } from '../schema/SettingsPlist';
|
||||
import schema from '../schema/SettingsPlist.json';
|
||||
|
||||
export function createModSetForSettingsPage({ name }: { name: string }) {
|
||||
const customModName = "settings" + name + "Plist";
|
||||
const customModName = 'settings' + name + 'Plist';
|
||||
|
||||
const withSettingsPlist: ConfigPlugin<Mod<SettingsPlist>> = (
|
||||
config,
|
||||
action,
|
||||
) => {
|
||||
const withSettingsPlist: ConfigPlugin<Mod<SettingsPlist>> = (config, action) => {
|
||||
return withMod(config, {
|
||||
platform: "ios",
|
||||
platform: 'ios',
|
||||
mod: customModName,
|
||||
action,
|
||||
});
|
||||
@@ -29,7 +26,7 @@ export function createModSetForSettingsPage({ name }: { name: string }) {
|
||||
|
||||
const withBaseModInternal: ConfigPlugin = (config) => {
|
||||
return BaseMods.withGeneratedBaseMods(config, {
|
||||
platform: "ios",
|
||||
platform: 'ios',
|
||||
saveToInternal: true,
|
||||
skipEmptyMod: false,
|
||||
providers: {
|
||||
@@ -40,7 +37,7 @@ export function createModSetForSettingsPage({ name }: { name: string }) {
|
||||
return path.join(
|
||||
modRequest.platformProjectRoot,
|
||||
modRequest.projectName!,
|
||||
`Settings.bundle/${name}.plist`,
|
||||
`Settings.bundle/${name}.plist`
|
||||
);
|
||||
},
|
||||
async read(filePath) {
|
||||
@@ -50,12 +47,10 @@ export function createModSetForSettingsPage({ name }: { name: string }) {
|
||||
PreferenceSpecifiers: [],
|
||||
};
|
||||
}
|
||||
return plist.default.parse(
|
||||
await fs.promises.readFile(filePath, "utf-8"),
|
||||
);
|
||||
return plist.default.parse(await fs.promises.readFile(filePath, 'utf-8'));
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to parse the iOS Settings.bundle/${name}.plist: "${filePath}". ${error.message}}`,
|
||||
`Failed to parse the iOS Settings.bundle/${name}.plist: "${filePath}". ${error.message}}`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,28 +4,28 @@ import {
|
||||
BaseMods,
|
||||
createRunOncePlugin,
|
||||
withMod,
|
||||
} from "expo/config-plugins";
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
} from 'expo/config-plugins';
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import * as strings from "./strings";
|
||||
import * as strings from './strings';
|
||||
|
||||
export type SettingsStrings = Record<string, string>;
|
||||
|
||||
export function createModSetForSettingsStrings({
|
||||
name,
|
||||
lang = "en",
|
||||
lang = 'en',
|
||||
}: {
|
||||
/** Like `Root` */
|
||||
name: string;
|
||||
/** Like `en` */
|
||||
lang?: string;
|
||||
}) {
|
||||
const customModName = "settings" + lang + "_" + name + "Strings";
|
||||
const customModName = 'settings' + lang + '_' + name + 'Strings';
|
||||
|
||||
const withBaseModInternal: ConfigPlugin = (config) => {
|
||||
return BaseMods.withGeneratedBaseMods(config, {
|
||||
platform: "ios",
|
||||
platform: 'ios',
|
||||
saveToInternal: true,
|
||||
skipEmptyMod: false,
|
||||
providers: {
|
||||
@@ -35,7 +35,7 @@ export function createModSetForSettingsStrings({
|
||||
return path.join(
|
||||
modRequest.platformProjectRoot,
|
||||
modRequest.projectName!,
|
||||
`Settings.bundle/${lang}.lproj/${name}.strings`,
|
||||
`Settings.bundle/${lang}.lproj/${name}.strings`
|
||||
);
|
||||
},
|
||||
async read(filePath) {
|
||||
@@ -43,13 +43,10 @@ export function createModSetForSettingsStrings({
|
||||
if (fs.existsSync(filePath) === false) {
|
||||
return {};
|
||||
}
|
||||
return strings.openAsync(filePath, false) as unknown as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
return strings.openAsync(filePath, false) as unknown as Record<string, string>;
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to parse the Settings.bundle/${lang}.lproj/${name}.strings: "${filePath}". ${error.message}}`,
|
||||
`Failed to parse the Settings.bundle/${lang}.lproj/${name}.strings: "${filePath}". ${error.message}}`
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -70,7 +67,7 @@ export function createModSetForSettingsStrings({
|
||||
|
||||
const withStrings: ConfigPlugin<Mod<SettingsStrings>> = (config, action) => {
|
||||
return withMod(config, {
|
||||
platform: "ios",
|
||||
platform: 'ios',
|
||||
mod: customModName,
|
||||
action,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { XcodeProject } from "@bacons/xcode";
|
||||
import * as xcodeParse from "@bacons/xcode/json";
|
||||
import { XcodeProject } from '@bacons/xcode';
|
||||
import * as xcodeParse from '@bacons/xcode/json';
|
||||
import {
|
||||
type Mod,
|
||||
BaseMods,
|
||||
@@ -7,17 +7,14 @@ import {
|
||||
createRunOncePlugin,
|
||||
IOSConfig,
|
||||
withMod,
|
||||
} from "expo/config-plugins";
|
||||
import * as fs from "fs";
|
||||
} from 'expo/config-plugins';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const customModName = "xcodeProjectBeta-apple-settings";
|
||||
const customModName = 'xcodeProjectBeta-apple-settings';
|
||||
|
||||
export const withXcodeProjectBeta: ConfigPlugin<Mod<XcodeProject>> = (
|
||||
config,
|
||||
action,
|
||||
) => {
|
||||
export const withXcodeProjectBeta: ConfigPlugin<Mod<XcodeProject>> = (config, action) => {
|
||||
return withMod(config, {
|
||||
platform: "ios",
|
||||
platform: 'ios',
|
||||
mod: customModName,
|
||||
action,
|
||||
});
|
||||
@@ -25,7 +22,7 @@ export const withXcodeProjectBeta: ConfigPlugin<Mod<XcodeProject>> = (
|
||||
|
||||
const withXcodeProjectBetaBaseModInternal: ConfigPlugin = (config) => {
|
||||
return BaseMods.withGeneratedBaseMods(config, {
|
||||
platform: "ios",
|
||||
platform: 'ios',
|
||||
saveToInternal: true,
|
||||
skipEmptyMod: false,
|
||||
providers: {
|
||||
@@ -40,9 +37,7 @@ const withXcodeProjectBetaBaseModInternal: ConfigPlugin = (config) => {
|
||||
try {
|
||||
return XcodeProject.open(filePath);
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to parse the Xcode project: "${filePath}". ${error.message}}`,
|
||||
);
|
||||
throw new Error(`Failed to parse the Xcode project: "${filePath}". ${error.message}}`);
|
||||
}
|
||||
},
|
||||
async write(filePath, { modResults, modRequest: { introspect } }) {
|
||||
@@ -61,5 +56,5 @@ const withXcodeProjectBetaBaseModInternal: ConfigPlugin = (config) => {
|
||||
|
||||
export const withXcodeProjectBetaBaseMod = createRunOncePlugin(
|
||||
withXcodeProjectBetaBaseModInternal,
|
||||
"withXcodeProjectBetaBaseMod",
|
||||
'withXcodeProjectBetaBaseMod'
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { withStaticSettings, StaticSettings, AppleLocale } from "./static";
|
||||
import { withStaticSettings, StaticSettings, AppleLocale } from './static';
|
||||
|
||||
export {
|
||||
ChildPane,
|
||||
@@ -9,10 +9,10 @@ export {
|
||||
TextField,
|
||||
Title,
|
||||
MultiValue,
|
||||
} from "./models";
|
||||
} from './models';
|
||||
|
||||
export { StaticSettings, AppleLocale };
|
||||
export type { StaticSettings, AppleLocale };
|
||||
|
||||
export * from "./schema/SettingsPlist";
|
||||
export * from './schema/SettingsPlist';
|
||||
|
||||
export default withStaticSettings;
|
||||
Loaded 100 of 248 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user