A highly customizable OTA (Over-The-Air) update UI package for Expo apps. Includes animated banners, info screens, and full theming support.
📚 Full Documentation | 📦 npm Package | 💻 GitHub
|
See exactly what's eating your storage — and fix it in seconds Photo Trim gives you complete visibility into your photo library clutter. Discover large files, old media, and forgotten videos buried in both your device and iCloud. Smart grouping and powerful filters make it effortless to identify and delete unnecessary photos and videos — reclaiming gigabytes of storage with just a few taps.
🌐 Visit photo-trim.com · 📱 Download on App Store |
- 🎨 Fully Customizable — Theme colors, gradients, border radius, and animations
- 🌍 i18n Ready — Pass your own translations or use English defaults
- ✨ Animated Banner — Beautiful gradient banner with pulse animation
- 📱 Info Screen — Full debug/info screen with changelog display
- 🔌 Drop-in Ready — Works out of the box with sensible defaults
- 🎯 Render Props — Override any component with your own implementation
- 🎨 Icon Support — Uses lucide-react-native with text fallbacks
- 🚀 Easy Publishing — Simple
ota-publishcommand to publish OTA updates - ⚙️ Configurable — Customize via
ota-updates.config.js - 📊 Multiple Version Strategies — Semver, build number, or date-based
- 📝 Smart Changelog — Auto-generate from git, manual input, or file
- 🎯 Interactive Mode — Guided prompts for easy publishing
- 🔍 Dry Run — Preview changes before publishing
- 🪝 Hooks System — Run custom logic before/after publish
- 📦 Multi-Channel — Support for dev, preview, production channels
|
Animated Update Banner Beautiful gradient with pulse animation |
Download Progress Real-time download status |
OTA Info Screen Full debug & version details |
Interactive CLI — Publish OTA updates with guided prompts, version tracking, and changelog generation
# Using bun
bun add @ddedic/expo-fancy-ota-updates
# Using npm
npm install @ddedic/expo-fancy-ota-updates
# Using yarn
yarn add @ddedic/expo-fancy-ota-updatesThe following dependencies are required:
bun add expo expo-updates expo-device react react-native react-native-reanimated react-native-safe-area-contextOptional dependencies for enhanced visuals:
# For gradient banners
bun add expo-linear-gradient
# For beautiful icons (recommended)
bun add lucide-react-native react-native-svgCreate an ota-version.json file in your project root (this gets updated by your OTA publishing workflow):
{
"version": "1.0.0",
"buildNumber": 1,
"releaseDate": "2025-01-01T00:00:00.000Z",
"changelog": ["Initial release"]
}// App.tsx or _layout.tsx
import {
OTAUpdatesProvider,
UpdateBanner
} from '@ddedic/expo-fancy-ota-updates';
import versionData from './ota-version.json';
export default function App() {
return (
<OTAUpdatesProvider config={{ versionData }}>
{/* Banner auto-shows when update is available */}
<UpdateBanner />
{/* Your app content */}
<YourApp />
</OTAUpdatesProvider>
);
}That's it! The banner will automatically appear when an OTA update is detected.
Comprehensive documentation with guides, examples, and API reference:
- Getting Started — Quick setup guide
- UI Components — Provider, Banner, Info Screen, Hook
- CLI Tool — Publishing commands and configuration
- Guides — Theming, i18n, Hooks
- Examples — Complete workflows and custom UI
This package includes a powerful CLI for publishing OTA updates with version tracking.
In your project (recommended):
# When installed in your project, just use:
npx ota-publish --channel productionGlobal usage:
# If running outside a project, specify the package:
npx -p @ddedic/expo-fancy-ota-updates ota-publish --channel production
# Or install globally:
npm install -g @ddedic/expo-fancy-ota-updates
ota-publish --channel productionAfter publishing to npm:
npx ota-publish --channel developmentFor local development (before npm publish):
Add to your package.json:
{
"scripts": {
"ota:dev": "node node_modules/@ddedic/expo-fancy-ota-updates/bin/ota-publish.js --channel development",
"ota:preview": "node node_modules/@ddedic/expo-fancy-ota-updates/bin/ota-publish.js --channel preview",
"ota:prod": "node node_modules/@ddedic/expo-fancy-ota-updates/bin/ota-publish.js --channel production"
}
}Then run: npm run ota:dev, npm run ota:preview, or npm run ota:prod
The main provider that enables OTA update functionality throughout your app.
| Prop | Type | Description |
|---|---|---|
children |
ReactNode |
Your app content |
theme |
Partial<OTATheme> |
Custom theme (merged with defaults) |
translations |
Partial<OTATranslations> |
Custom translations (merged with defaults) |
config |
OTAConfig |
Provider behavior configuration |
interface OTAConfig {
checkOnMount?: boolean; // Check for updates on mount (default: true)
checkOnForeground?: boolean; // Check when app comes to foreground (default: true)
autoDownload?: boolean; // Auto-download when available (default: false)
autoReload?: boolean; // Auto-reload after download (default: false)
versionData?: OTAVersionData; // Version info from ota-version.json
debug?: boolean; // Enable debug logging (default: __DEV__)
}<OTAUpdatesProvider
theme={{
colors: {
primary: '#6366F1',
primaryLight: '#818CF8',
background: '#0B0B0F',
text: '#FFFFFF',
},
bannerGradient: ['#6366F1', '#818CF8'],
borderRadius: 16,
}}
translations={{
banner: {
updateAvailable: 'New Update Available!',
updateButton: 'Update Now',
},
}}
config={{
checkOnMount: true,
checkOnForeground: true,
autoDownload: false,
versionData: require('./ota-version.json'),
}}
>
{children}
</OTAUpdatesProvider>Access OTA update state and actions from any component within the provider.
import { useOTAUpdates } from '@ddedic/expo-fancy-ota-updates';
function MyComponent() {
const {
// State
isUpdateAvailable, // boolean - Update is available to download
isDownloading, // boolean - Currently downloading
isDownloaded, // boolean - Download complete, ready to apply
status, // 'idle' | 'checking' | 'available' | 'downloading' | 'downloaded' | 'error'
checkError, // Error | null
downloadError, // Error | null
lastCheck, // Date | null
// expo-updates Metadata
currentUpdateId, // string | null
channel, // string | null
runtimeVersion, // string | null
isEmbeddedUpdate, // boolean
// Version Data
otaVersion, // string - e.g., "1.0.0-production.29"
otaBuildNumber, // number
otaReleaseDate, // string - ISO date
otaChangelog, // string[]
// Actions
checkForUpdate, // () => Promise<void>
downloadUpdate, // () => Promise<void>
reloadApp, // () => Promise<void>
simulateUpdate, // () => void - For testing
// Theming
theme, // OTATheme
translations, // OTATranslations
} = useOTAUpdates();
}Animated banner that appears when updates are available.
| Prop | Type | Description |
|---|---|---|
style |
object |
Custom container style |
visible |
boolean |
Controlled visibility mode |
onDismiss |
() => void |
Called when banner is dismissed |
renderBanner |
(props) => ReactNode |
Custom render function |
// Auto-shows when update available
<UpdateBanner />const [showBanner, setShowBanner] = useState(false);
<UpdateBanner
visible={showBanner}
onDismiss={() => setShowBanner(false)}
/><UpdateBanner
renderBanner={({
isDownloaded,
isDownloading,
otaVersion,
onUpdate,
onRestart,
onDismiss,
theme
}) => (
<View style={{ backgroundColor: theme.colors.primary }}>
<Text>{isDownloaded ? 'Ready!' : `v${otaVersion} available`}</Text>
<Button
title={isDownloaded ? 'Restart' : 'Update'}
onPress={isDownloaded ? onRestart : onUpdate}
/>
<Button title="Later" onPress={onDismiss} />
</View>
)}
/>Full debug/info screen for OTA updates. Great for settings or debug menus.
| Prop | Type | Description |
|---|---|---|
mode |
'developer' | 'user' |
Screen mode (default: 'developer') |
onBack |
() => void |
Back navigation callback |
renderHeader |
(props) => ReactNode |
Custom header render |
renderInfo |
(props) => ReactNode |
Custom render for Info section |
renderActions |
(props) => ReactNode |
Custom render for Actions section |
renderChangelog |
(props) => ReactNode |
Custom render for Changelog section |
showRuntimeVersion |
boolean |
Toggle runtime version visibility |
showOtaVersion |
boolean |
Toggle OTA version visibility |
showReleaseDate |
boolean |
Toggle release date visibility |
showUpdateId |
boolean |
Toggle update ID visibility |
showCheckButton |
boolean |
Toggle "Check for Updates" button |
showDownloadButton |
boolean |
Toggle "Download" button |
showReloadButton |
boolean |
Toggle "Reload" button |
showDebugSection |
boolean |
Toggle debug actions section |
style |
object |
Custom container style |
import { OTAInfoScreen } from '@ddedic/expo-fancy-ota-updates';
// Simple usage
<OTAInfoScreen
mode="user"
onBack={() => navigation.goBack()}
/>
// Advanced usage with custom sections
<OTAInfoScreen
renderInfo={({ theme }) => (
<View style={{ padding: 20 }}>
<Text style={{ color: theme.colors.text }}>My Custom Header</Text>
</View>
)}
/>You can also import sub-components directly if you want to compose your own screen:
import { OTAUpdateInfo, OTAUpdateActions, OTAUpdateChangelog } from '@ddedic/expo-fancy-ota-updates';interface OTATheme {
colors: {
primary: string; // Main brand color
primaryLight: string; // Lighter variant
background: string; // Screen background
backgroundSecondary: string;
backgroundTertiary: string;
text: string; // Primary text
textSecondary: string;
textTertiary: string;
border: string;
error: string;
success: string;
warning: string;
};
bannerGradient?: [string, string]; // Gradient start/end
borderRadius?: number; // Card border radius
buttonBorderRadius?: number; // Button border radius
animation?: {
duration?: number; // Enter/exit animation (ms)
pulseDuration?: number; // Pulse animation cycle (ms)
};
}import {
OTAUpdatesProvider,
defaultTheme, // Dark indigo theme
lightTheme, // Light theme
} from '@ddedic/expo-fancy-ota-updates';
// Use light theme
<OTAUpdatesProvider theme={lightTheme}>
// Or customize from defaults
<OTAUpdatesProvider theme={{
...defaultTheme,
colors: {
...defaultTheme.colors,
primary: '#10B981',
},
}}>interface OTATranslations {
banner: {
updateAvailable: string;
updateReady: string;
downloading: string;
versionAvailable: string;
restartToApply: string;
updateButton: string;
restartButton: string;
};
infoScreen: {
title: string;
statusTitle: string;
embeddedBuild: string;
otaUpdate: string;
runtimeVersion: string;
otaVersion: string;
releaseDate: string;
updateId: string;
channel: string;
whatsNew: string;
checkForUpdates: string;
downloadUpdate: string;
reloadApp: string;
debugTitle: string;
simulateUpdate: string;
devMode: string;
notAvailable: string;
none: string;
};
}<OTAUpdatesProvider
translations={{
banner: {
updateAvailable: 'Neue Version verfügbar',
updateReady: 'Update bereit',
downloading: 'Download läuft...',
versionAvailable: 'Eine neue Version ist verfügbar',
restartToApply: 'Neustart zum Anwenden',
updateButton: 'Aktualisieren',
restartButton: 'Neustart',
},
infoScreen: {
title: 'OTA Updates',
checkForUpdates: 'Nach Updates suchen',
// ... more translations
},
}}
>import { useTheme } from './your-theme-context';
function AppProviders({ children }) {
const { colors, isDark } = useTheme();
return (
<OTAUpdatesProvider
theme={{
colors: {
primary: colors.primary,
background: colors.background,
text: colors.text,
// Map your theme colors
},
}}
>
{children}
</OTAUpdatesProvider>
);
}import { useTranslation } from 'react-i18next';
function AppProviders({ children }) {
const { t } = useTranslation('ota');
return (
<OTAUpdatesProvider
translations={{
banner: {
updateAvailable: t('banner.updateAvailable'),
updateButton: t('banner.updateButton'),
// ...
},
}}
>
{children}
</OTAUpdatesProvider>
);
}MIT License © 2025 Danijel Dedic, Technabit e.U.
Contributions are welcome! Please feel free to submit a Pull Request.