Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions .github/workflows/beta-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
name: Beta Release
on:
push:
branches:
- beta
pull_request:
branches:
- beta
merge_group:
types:
- checks_requested

jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: Lint files
run: yarn lint

- name: Typecheck files
run: yarn typecheck

test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: Run unit tests
run: yarn test --maxWorkers=2 --coverage

build-library:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: Build package
run: yarn prepare

build-android:
runs-on: ubuntu-latest
env:
TURBO_CACHE_DIR: .turbo/android
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: Cache turborepo for Android
uses: actions/cache@v4
with:
path: ${{ env.TURBO_CACHE_DIR }}
key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-turborepo-android-

- name: Check turborepo cache for Android
run: |
TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status")

if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
echo "turbo_cache_hit=1" >> $GITHUB_ENV
fi

- name: Install JDK
if: env.turbo_cache_hit != 1
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'

- name: Finalize Android SDK
if: env.turbo_cache_hit != 1
run: |
/bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null"

- name: Cache Gradle
if: env.turbo_cache_hit != 1
uses: actions/cache@v4
with:
path: |
~/.gradle/wrapper
~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Build example for Android
env:
JAVA_OPTS: "-XX:MaxHeapSize=6g"
run: |
yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"

build-ios:
runs-on: macos-latest
env:
TURBO_CACHE_DIR: .turbo/ios
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: Cache turborepo for iOS
uses: actions/cache@v4
with:
path: ${{ env.TURBO_CACHE_DIR }}
key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-turborepo-ios-

- name: Check turborepo cache for iOS
run: |
TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status")

if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
echo "turbo_cache_hit=1" >> $GITHUB_ENV
fi

- name: Cache cocoapods
if: env.turbo_cache_hit != 1
id: cocoapods-cache
uses: actions/cache@v4
with:
path: |
**/ios/Pods
key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-cocoapods-

- name: Install cocoapods
if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
run: |
cd example/ios
pod install
env:
NO_FLIPPER: 1

- name: Build example for iOS
run: |
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" 2>&1 | grep -i error > ios_errorlog.txt

- name: Upload iOS error log
uses: actions/upload-artifact@v4
with:
name: ios-error-log
path: ios_errorlog.txt

publish-beta:
needs: [lint, test, build-library, build-android, build-ios]
runs-on: ubuntu-latest
permissions:
contents: write # To publish a GitHub release
issues: write # To comment on released issues
pull-requests: write # To comment on released pull requests
id-token: write # To enable use of OIDC for npm provenance
if: github.ref == 'refs/heads/beta'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensures all tags are fetched

- name: Setup
uses: ./.github/actions/setup

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*" # Use the latest LTS version of Node.js
registry-url: 'https://registry.npmjs.org/' # Specify npm registry

- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
run: npm audit signatures # Check the signatures to verify integrity

- name: Release Beta
run: npx semantic-release # Run semantic-release to manage versioning and publishing
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub token for authentication

# Why NODE_AUTH_TOKEN instead of NPM_TOKEN: https://github.com/semantic-release/semantic-release/issues/2313
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # npm token for publishing package
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# [1.2.0-beta.2](https://github.com/JairajJangle/react-native-navigation-mode/compare/v1.2.0-beta.1...v1.2.0-beta.2) (2025-07-21)


### Bug Fixes

* expo managed workflow incorrectly loading es module from common js context ([29d171f](https://github.com/JairajJangle/react-native-navigation-mode/commit/29d171f3ce9fd818340ecb0d8d6bfb82042fed0d))

# [1.2.0-beta.1](https://github.com/JairajJangle/react-native-navigation-mode/compare/v1.1.1...v1.2.0-beta.1) (2025-07-21)


### Features

* added support for expo managed workflows ([b60fc17](https://github.com/JairajJangle/react-native-navigation-mode/commit/b60fc17bbfb8d2682d5bc4c3840ce085c1d94a0a))

## [1.1.1](https://github.com/JairajJangle/react-native-navigation-mode/compare/v1.1.0...v1.1.1) (2025-07-17)


Expand Down
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

🧭 Detect Android navigation mode (3-button, 2-button, or gesture navigation) with native precision using Turbo modules.

[![npm version](https://img.shields.io/npm/v/react-native-navigation-mode)](https://badge.fury.io/js/react-native-navigation-mode) [![License](https://img.shields.io/github/license/JairajJangle/react-native-navigation-mode)](https://github.com/JairajJangle/react-native-navigation-mode/blob/main/LICENSE) [![Workflow Status](https://github.com/JairajJangle/react-native-navigation-mode/actions/workflows/ci.yml/badge.svg)](https://github.com/JairajJangle/react-native-navigation-mode/actions/workflows/ci.yml) ![Android](https://img.shields.io/badge/-Android-555555?logo=android&logoColor=3DDC84) ![iOS](https://img.shields.io/badge/-iOS-555555?logo=apple&logoColor=white) [![GitHub issues](https://img.shields.io/github/issues/JairajJangle/react-native-navigation-mode)](https://github.com/JairajJangle/react-native-navigation-mode/issues?q=is%3Aopen+is%3Aissue) ![TS](https://img.shields.io/badge/TypeScript-strict_💪-blue) ![Turbo Module](https://img.shields.io/badge/Turbo%20Module-⚡-orange) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-native-navigation-mode)
[![npm version](https://img.shields.io/npm/v/react-native-navigation-mode)](https://badge.fury.io/js/react-native-navigation-mode) [![License](https://img.shields.io/github/license/JairajJangle/react-native-navigation-mode)](https://github.com/JairajJangle/react-native-navigation-mode/blob/main/LICENSE) [![Workflow Status](https://github.com/JairajJangle/react-native-navigation-mode/actions/workflows/ci.yml/badge.svg)](https://github.com/JairajJangle/react-native-navigation-mode/actions/workflows/ci.yml) ![Android](https://img.shields.io/badge/-Android-555555?logo=android&logoColor=3DDC84) ![iOS](https://img.shields.io/badge/-iOS-555555?logo=apple&logoColor=white) [![GitHub issues](https://img.shields.io/github/issues/JairajJangle/react-native-navigation-mode)](https://github.com/JairajJangle/react-native-navigation-mode/issues?q=is%3Aopen+is%3Aissue) ![TS](https://img.shields.io/badge/TypeScript-strict_💪-blue) ![Turbo Module](https://img.shields.io/badge/Turbo%20Module-⚡-orange) ![Expo](https://img.shields.io/badge/Expo-SDK_52+-000020?logo=expo) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-native-navigation-mode)

<table align="center">
<tr>
Expand Down Expand Up @@ -45,11 +45,14 @@
- 📦 **Zero Dependencies** - Lightweight and performant
- 🛡️ **TypeScript** - Full type safety out of the box
- ↕️ **Edge To Edge Support** - Full support for `react-native-edge-to-edge`
- 📲 **Expo Support** - Works with Expo SDK 52+ managed workflow and development builds

## 🚀 Quick Start

### Installation

#### React Native (Bare Workflow)

Using yarn:

```sh
Expand All @@ -64,6 +67,44 @@ npm install react-native-navigation-mode

> **Note:** Auto-linking should handle setup automatically for all newer RN versions.

#### Expo (Managed Workflow)

```sh
npx expo install react-native-navigation-mode
```

##### Expo Configuration

Add the plugin to your `app.json` or `app.config.ts`:

```json
{
"expo": {
"plugins": [
"react-native-navigation-mode"
]
}
}
```

For bare workflow or custom native code, you'll need to prebuild:

```sh
npx expo prebuild
```

##### Development Builds

Since this library contains native code, it requires a custom development build. You cannot use it with standard Expo Go.

#### Requirements

- **React Native**: 0.77.0+
- **Expo SDK**: 52+ (for managed workflow)
- **Android**: API 21+ (Android 5.0+)
- **iOS**: Any version (returns gesture navigation)
- **New Architecture**: Required (enabled by default in RN 0.77+ and Expo SDK 52+)

---

### Basic Usage
Expand Down Expand Up @@ -275,7 +316,7 @@ This library uses **official Android APIs** to directly query the system's navig
With Android 15 enforcing edge-to-edge display for apps targeting API 35 and Google mandating this for Play Store updates starting August 31, 2025, proper navigation detection is now **essential**:

- **Edge-to-edge enforcement** - Android 16 will remove the opt-out entirely
- **Expo SDK 53+** - New projects use edge-to-edge by default
- **Expo SDK 52+** - New projects use edge-to-edge by default
- **React Native 0.79+** - Built-in support for 16KB page size and edge-to-edge
- **Safe area management** - Critical for preventing content overlap with system bars

Expand Down Expand Up @@ -307,6 +348,7 @@ const isGesture = await isGestureNavigation(); // 🎯 Always accurate
| -------- | ------------ | ------------------------------------------------------------ |
| Android | ✅ Full | Detects all navigation modes and navigation bar height via native Android APIs |
| iOS | ✅ Compatible | Always returns `gesture` and `navigationBarHeight: 0` (iOS uses gesture navigation) |
| Expo | ✅ Full | Supported in managed workflow with SDK 52+ |

### Android Compatibility

Expand Down Expand Up @@ -352,6 +394,12 @@ The library uses multiple detection methods for maximum accuracy:
- This is normal on devices without navigation bars (some tablets)
- On older Android versions, fallback detection may not work on all devices

**Expo: "Package does not contain a valid config plugin"**

- Ensure you've installed the latest version of the library
- Try clearing your cache: `npx expo start --clear`
- Make sure the plugin is added to your `app.json`

## 🤝 Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
15 changes: 15 additions & 0 deletions app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { createRunOncePlugin } = require('@expo/config-plugins');

/**
* Expo Config Plugin for react-native-navigation-mode
* This plugin ensures the native module is properly linked in managed workflow
*/
const withNavigationMode = (config) => {
// No additional configuration needed for this module
// The module will be auto-linked through the Expo modules system
return config;
};

const pkg = require('./package.json');

module.exports = createRunOncePlugin(withNavigationMode, pkg.name, pkg.version);
4 changes: 3 additions & 1 deletion example/ios/NavigationModeExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
5DCACB8F33CDC322A6C60F78 /* libPods-NavigationModeExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NavigationModeExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NavigationModeExample/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NavigationModeExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
98B434302E2E8FB4004B224E /* hermes.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = hermes.xcframework; path = "Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -54,6 +55,7 @@
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
98B434302E2E8FB4004B224E /* hermes.xcframework */,
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
5DCACB8F33CDC322A6C60F78 /* libPods-NavigationModeExample.a */,
);
Expand Down Expand Up @@ -185,7 +187,7 @@
};
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 12;
files = (
);
inputFileListPaths = (
Expand Down
Loading
Loading