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
384 changes: 384 additions & 0 deletions .oxlintrc.json

Large diffs are not rendered by default.

35 changes: 33 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions packages/android-client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@use-voltra/android-client",
"version": "1.3.1",
"description": "Client-only Voltra APIs for Android",
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"types": "build/types/index.d.ts",
"exports": {
".": {
"types": "./build/types/index.d.ts",
"require": "./build/cjs/index.js",
"import": "./build/esm/index.js",
"default": "./build/esm/index.js"
},
"./package.json": "./package.json"
},
"files": [
"build",
"README.md"
],
"scripts": {
"build": "node ../../scripts/build-package.mjs packages/android-client",
"clean": "rm -rf build",
"lint": "oxlint src",
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
},
"dependencies": {
"@use-voltra/android": "1.3.1"
},
"keywords": [
"react-native",
"expo",
"voltra",
"android",
"widget"
],
"author": "Saúl Sharma (https://x.com/saul_sharma), Szymon Chmal (https://x.com/chmalszymon)",
"repository": {
"type": "git",
"url": "git+https://github.com/callstackincubator/voltra.git",
"directory": "packages/android-client"
},
"bugs": {
"url": "https://github.com/callstackincubator/voltra/issues"
},
"license": "MIT",
"homepage": "https://use-voltra.dev",
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { requireNativeModule } from 'expo'
import type {
StartAndroidOngoingNotificationOptions,
UpdateAndroidOngoingNotificationOptions,
} from './live-update/types.js'
} from '@use-voltra/android'
import type { EventSubscription, PreloadImageOptions, PreloadImagesResult, WidgetServerCredentials } from './types.js'

export interface VoltraAndroidModuleSpec {
startAndroidLiveUpdate(payload: string, options: { updateName?: string; channelId?: string }): Promise<string>
updateAndroidLiveUpdate(notificationId: string, payload: string): Promise<void>
stopAndroidLiveUpdate(notificationId: string): Promise<void>
isAndroidLiveUpdateActive(updateName: string): boolean
endAllAndroidLiveUpdates(): Promise<void>
startAndroidOngoingNotification(
payload: string,
options: StartAndroidOngoingNotificationOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,31 @@
import { requireNativeView } from 'expo'
import React, { ReactNode, useEffect, useMemo } from 'react'
import { StyleProp, ViewStyle } from 'react-native'
import React, { type ReactNode, useEffect, useMemo } from 'react'
import { type StyleProp, type ViewStyle } from 'react-native'

import { addVoltraListener, VoltraInteractionEvent } from '../events.js'
import { renderAndroidViewToJson } from '../widgets/renderer.js'
import { renderAndroidViewToJson } from '@use-voltra/android'

import { addVoltraListener, type VoltraInteractionEvent } from '../events.js'

const NativeVoltraView = requireNativeView('VoltraModule')

// Generate a unique ID for views that don't have one
const generateViewId = () => `voltra-view-android-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`

export type VoltraViewProps = {
/**
* Unique identifier for this view instance.
* Used as 'source' in interaction events to identify which view triggered the event.
* If not provided, a unique ID will be generated automatically.
*/
id?: string
/**
* Voltra JSX components to render
*/
children: ReactNode
/**
* Style for the view container
*/
style?: StyleProp<ViewStyle>
/**
* Callback when user interacts with components in the view.
* Events are filtered by this view's id (source).
*/
onInteraction?: (event: VoltraInteractionEvent) => void
}

/**
* A React Native component that renders Voltra UI for Android using Jetpack Compose.
*/
export function VoltraView({ id, children, style, onInteraction }: VoltraViewProps) {
// Generate a stable ID if not provided
const viewId = useMemo(() => id || generateViewId(), [id])

const payload = useMemo(() => JSON.stringify(renderAndroidViewToJson(children)), [children])

// Subscribe to interaction events and filter by this view's ID
useEffect(() => {
if (!onInteraction) return

const subscription = addVoltraListener('interaction', (event) => {
// Only forward events from this view
if (event.source === viewId) {
onInteraction({
type: 'interaction',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import React from 'react'
import { StyleProp, ViewStyle } from 'react-native'
import { type StyleProp, type ViewStyle } from 'react-native'

import { VoltraView, VoltraViewProps } from './VoltraView.js'
import { VoltraView, type VoltraViewProps } from './VoltraView.js'

/**
* Android-specific widget sizes in dp.
*/
export type AndroidWidgetFamily = 'small' | 'mediumSquare' | 'mediumWide' | 'mediumTall' | 'large' | 'extraLarge'

const WIDGET_DIMENSIONS: Record<AndroidWidgetFamily, { width: number; height: number }> = {
Expand All @@ -18,19 +15,10 @@ const WIDGET_DIMENSIONS: Record<AndroidWidgetFamily, { width: number; height: nu
}

export type VoltraWidgetPreviewProps = Omit<VoltraViewProps, 'style'> & {
/**
* Android widget size to preview
*/
family: AndroidWidgetFamily
/**
* Additional styles to apply on top of the widget dimensions
*/
style?: StyleProp<ViewStyle>
}

/**
* A preview component that renders Voltra Android JSX content at specific dimensions.
*/
export function VoltraWidgetPreview({ family, style, children, ...voltraViewProps }: VoltraWidgetPreviewProps) {
const dimensions = WIDGET_DIMENSIONS[family]
const previewStyle: StyleProp<ViewStyle> = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,6 @@ export type VoltraEventMap = {
interaction: VoltraInteractionEvent
}

/**
* Add a listener for Voltra events.
*
* Supported events:
* - `interaction`: User interactions with widgets (buttons, switches, checkboxes) (iOS only)
* - `stateChange`: Live Activity state changes (iOS only)
* - `activityTokenReceived`: Push token for Live Activity (iOS only)
* - `activityPushToStartTokenReceived`: Push-to-start token (iOS only)
*
* Note: On Android, interactions open the app directly (optionally via deep links)
* instead of emitting background events.
*
* @param event The event type to listen for
* @param listener Callback function to handle the event
* @returns EventSubscription with a remove() method to unsubscribe
*/
export function addVoltraListener<K extends keyof VoltraEventMap>(
event: K,
listener: (event: VoltraEventMap[K]) => void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
export { AndroidOngoingNotification } from './live-update/components.js'

// Android ongoing notification API and types
export {
endAllAndroidLiveUpdates,
isAndroidLiveUpdateActive,
startAndroidLiveUpdate,
stopAndroidLiveUpdate,
updateAndroidLiveUpdate,
useAndroidLiveUpdate,
} from './live-update/api.js'
export type {
AndroidLiveUpdateJson,
AndroidLiveUpdateVariants,
AndroidLiveUpdateVariantsJson,
StartAndroidLiveUpdateOptions,
UpdateAndroidLiveUpdateOptions,
UseAndroidLiveUpdateOptions,
UseAndroidLiveUpdateResult,
} from './live-update/types.js'
export { AndroidOngoingNotification, renderAndroidOngoingNotificationPayload } from '@use-voltra/android'
export {
canPostPromotedAndroidNotifications,
endAllAndroidOngoingNotifications,
Expand All @@ -9,14 +24,13 @@ export {
hasAndroidNotificationPermission,
isAndroidOngoingNotificationActive,
openAndroidNotificationSettings,
renderAndroidOngoingNotificationPayload,
requestAndroidNotificationPermission,
startAndroidOngoingNotification,
stopAndroidOngoingNotification,
upsertAndroidOngoingNotification,
updateAndroidOngoingNotification,
useAndroidOngoingNotification,
} from './live-update/api.js'
} from './ongoing-notification/api.js'
export type {
AndroidOngoingNotificationActionPayload,
AndroidOngoingNotificationActionProps,
Expand All @@ -41,9 +55,7 @@ export type {
UpdateAndroidOngoingNotificationOptions,
UseAndroidOngoingNotificationOptions,
UseAndroidOngoingNotificationResult,
} from './live-update/types.js'

// Android Widget API and types
} from '@use-voltra/android'
export {
clearAllAndroidWidgets,
clearAndroidWidget,
Expand All @@ -58,19 +70,14 @@ export type {
AndroidWidgetVariants,
UpdateAndroidWidgetOptions,
WidgetInfo,
} from './widgets/types.js'

// Android Widget Server Credentials API
} from '@use-voltra/android'
export {
clearWidgetServerCredentials,
setWidgetServerCredentials,
type WidgetServerCredentials,
} from './widgets/server-credentials.js'

// Preload API
export { clearPreloadedImages, preloadImages, reloadWidgets } from './preload.js'

// Android Preview Components
export * from './events.js'
export { VoltraView, type VoltraViewProps } from './components/VoltraView.js'
export {
type AndroidWidgetFamily,
Expand Down
Loading
Loading