From 50b71a767a61c5f7c4bd09ddc55c4bd5dc25fdc2 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Thu, 2 Apr 2026 11:00:03 +0200 Subject: [PATCH 1/5] refactor: split platform client packages from universal APIs --- AGENTS.md | 58 +++++ packages/android-client/package.json | 53 ++++ .../src/VoltraModule.ts | 0 .../src/components/VoltraView.tsx | 31 +-- .../src/components/VoltraWidgetPreview.tsx | 16 +- .../{ios => android-client}/src/events.ts | 16 -- .../client.ts => android-client/src/index.ts} | 12 +- .../android-client/src/live-update/api.ts | 125 +++++++++ .../android-client/src/live-update/types.ts | 9 + packages/android-client/src/preload.ts | 32 +++ packages/android-client/src/types.ts | 23 ++ packages/android-client/src/utils/index.ts | 1 + .../src/utils/useUpdateOnHMR.ts | 6 - packages/android-client/src/widgets/api.ts | 51 ++++ .../src/widgets/server-credentials.ts | 14 - packages/android-client/tsconfig.base.json | 17 ++ packages/android-client/tsconfig.cjs.json | 9 + packages/android-client/tsconfig.esm.json | 9 + .../android-client/tsconfig.typecheck.json | 20 ++ packages/android-client/tsconfig.types.json | 10 + .../android-server/tsconfig.typecheck.json | 6 +- packages/android/package.json | 7 - packages/android/src/index.ts | 29 +++ packages/android/src/live-update/api.ts | 244 ------------------ packages/android/src/preload.ts | 72 ------ packages/android/src/types.ts | 39 ++- packages/android/src/utils/index.ts | 1 - packages/android/src/widgets/api.ts | 166 ------------ packages/android/src/widgets/index.ts | 18 -- packages/android/tsconfig.typecheck.json | 6 +- packages/core/package.json | 5 +- packages/core/src/index.ts | 1 - packages/core/src/types.ts | 30 --- packages/core/tsconfig.typecheck.json | 6 +- packages/expo-plugin/tsconfig.typecheck.json | 6 +- packages/ios-client/package.json | 53 ++++ packages/ios-client/src/VoltraModule.ts | 57 ++++ .../components/VoltraLiveActivityPreview.tsx | 29 +++ .../ios-client/src/components/VoltraView.tsx | 54 ++++ .../src/components/VoltraWidgetPreview.tsx | 38 +++ .../{android => ios-client}/src/events.ts | 16 -- packages/{ios => ios-client}/src/helpers.ts | 9 - .../src/client.ts => ios-client/src/index.ts} | 4 +- .../src/live-activity/api.ts | 162 +----------- packages/ios-client/src/logger.ts | 35 +++ packages/ios-client/src/preload.ts | 50 ++++ packages/ios-client/src/types.ts | 12 + .../src/utils/assertRunningOnApple.ts | 0 packages/ios-client/src/utils/helpers.ts | 1 + .../{ios => ios-client}/src/utils/index.ts | 0 .../ios-client/src/utils/useUpdateOnHMR.ts | 25 ++ .../src/widgets/server-credentials.ts | 16 ++ packages/ios-client/src/widgets/widget-api.ts | 65 +++++ packages/ios-client/tsconfig.base.json | 17 ++ packages/ios-client/tsconfig.cjs.json | 9 + packages/ios-client/tsconfig.esm.json | 9 + packages/ios-client/tsconfig.typecheck.json | 20 ++ packages/ios-client/tsconfig.types.json | 10 + packages/ios-server/tsconfig.typecheck.json | 6 +- packages/ios/package.json | 7 - packages/ios/src/VoltraModule.ts | 175 ------------- .../components/VoltraLiveActivityPreview.tsx | 54 ---- packages/ios/src/components/VoltraView.tsx | 88 ------- .../src/components/VoltraWidgetPreview.tsx | 64 ----- packages/ios/src/index.ts | 25 +- packages/ios/src/preload.ts | 112 -------- packages/ios/src/styles/index.ts | 2 +- packages/ios/src/styles/types.ts | 70 ++++- packages/ios/src/types.ts | 43 ++- packages/ios/src/utils/helpers.ts | 30 --- .../ios/src/widgets/server-credentials.ts | 61 ----- packages/ios/src/widgets/widget-api.ts | 203 --------------- packages/ios/tsconfig.typecheck.json | 6 +- packages/server/tsconfig.typecheck.json | 6 +- packages/voltra/jest.config.js | 12 +- packages/voltra/package.json | 2 + packages/voltra/src/android/client.ts | 2 +- .../src/android/components/VoltraView.tsx | 2 +- .../components/VoltraWidgetPreview.tsx | 2 +- packages/voltra/src/android/widgets/api.ts | 2 +- packages/voltra/src/client.ts | 4 +- packages/voltra/src/styles/index.ts | 2 +- packages/voltra/src/styles/types.ts | 2 +- packages/voltra/src/types.ts | 8 +- packages/voltra/tsconfig.base.json | 6 +- packages/voltra/tsconfig.typecheck.json | 6 +- tsconfig.json | 6 +- 87 files changed, 1173 insertions(+), 1674 deletions(-) create mode 100644 AGENTS.md create mode 100644 packages/android-client/package.json rename packages/{android => android-client}/src/VoltraModule.ts (100%) rename packages/{android => android-client}/src/components/VoltraView.tsx (53%) rename packages/{android => android-client}/src/components/VoltraWidgetPreview.tsx (73%) rename packages/{ios => android-client}/src/events.ts (70%) rename packages/{android/src/client.ts => android-client/src/index.ts} (86%) create mode 100644 packages/android-client/src/live-update/api.ts create mode 100644 packages/android-client/src/live-update/types.ts create mode 100644 packages/android-client/src/preload.ts create mode 100644 packages/android-client/src/types.ts create mode 100644 packages/android-client/src/utils/index.ts rename packages/{android => android-client}/src/utils/useUpdateOnHMR.ts (64%) create mode 100644 packages/android-client/src/widgets/api.ts rename packages/{android => android-client}/src/widgets/server-credentials.ts (53%) create mode 100644 packages/android-client/tsconfig.base.json create mode 100644 packages/android-client/tsconfig.cjs.json create mode 100644 packages/android-client/tsconfig.esm.json create mode 100644 packages/android-client/tsconfig.typecheck.json create mode 100644 packages/android-client/tsconfig.types.json delete mode 100644 packages/android/src/live-update/api.ts delete mode 100644 packages/android/src/preload.ts delete mode 100644 packages/android/src/widgets/api.ts delete mode 100644 packages/android/src/widgets/index.ts create mode 100644 packages/ios-client/package.json create mode 100644 packages/ios-client/src/VoltraModule.ts create mode 100644 packages/ios-client/src/components/VoltraLiveActivityPreview.tsx create mode 100644 packages/ios-client/src/components/VoltraView.tsx create mode 100644 packages/ios-client/src/components/VoltraWidgetPreview.tsx rename packages/{android => ios-client}/src/events.ts (70%) rename packages/{ios => ios-client}/src/helpers.ts (61%) rename packages/{ios/src/client.ts => ios-client/src/index.ts} (88%) rename packages/{ios => ios-client}/src/live-activity/api.ts (58%) create mode 100644 packages/ios-client/src/logger.ts create mode 100644 packages/ios-client/src/preload.ts create mode 100644 packages/ios-client/src/types.ts rename packages/{ios => ios-client}/src/utils/assertRunningOnApple.ts (100%) create mode 100644 packages/ios-client/src/utils/helpers.ts rename packages/{ios => ios-client}/src/utils/index.ts (100%) create mode 100644 packages/ios-client/src/utils/useUpdateOnHMR.ts create mode 100644 packages/ios-client/src/widgets/server-credentials.ts create mode 100644 packages/ios-client/src/widgets/widget-api.ts create mode 100644 packages/ios-client/tsconfig.base.json create mode 100644 packages/ios-client/tsconfig.cjs.json create mode 100644 packages/ios-client/tsconfig.esm.json create mode 100644 packages/ios-client/tsconfig.typecheck.json create mode 100644 packages/ios-client/tsconfig.types.json delete mode 100644 packages/ios/src/VoltraModule.ts delete mode 100644 packages/ios/src/components/VoltraLiveActivityPreview.tsx delete mode 100644 packages/ios/src/components/VoltraView.tsx delete mode 100644 packages/ios/src/components/VoltraWidgetPreview.tsx delete mode 100644 packages/ios/src/preload.ts delete mode 100644 packages/ios/src/utils/helpers.ts delete mode 100644 packages/ios/src/widgets/server-credentials.ts delete mode 100644 packages/ios/src/widgets/widget-api.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..99d3a8bd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +## Package Rules + +### `@use-voltra/core` + +- Role: generic renderer engine only. +- Allowed dependencies: `react`. +- Forbidden dependencies: `expo`, `react-native`, platform packages, client packages, server packages. +- Must contain only the JSX factory, renderer internals, payload helpers, and generic serialized node types. +- Must not expose platform-specific traits. + +### `@use-voltra/server` + +- Role: generic server handler utilities only. +- Forbidden dependencies: `expo`, `react-native`, client packages. + +### `@use-voltra/ios` + +- Role: server-safe iOS package. +- Allowed dependencies: `@use-voltra/core`, `react`. +- Forbidden dependencies: `expo`, `react-native`. +- Contains JSX primitives, iOS renderer wrappers, iOS render/model types, and iOS style types. + +### `@use-voltra/android` + +- Role: server-safe Android package. +- Allowed dependencies: `@use-voltra/core`, `react`. +- Forbidden dependencies: `expo`, `react-native`. +- Contains JSX primitives, Android renderer wrappers, Android render/model types, Android style types, dynamic color types, and server-safe internal helpers already consumed by `voltra`. + +### `@use-voltra/ios-client` + +- Role: client-only iOS package. +- Allowed dependencies: `@use-voltra/ios`, `react`, `expo`, `react-native`. +- Contains the native bridge, RN preview components, events, preload APIs, widget client APIs, live activity client APIs, and client-only public types. + +### `@use-voltra/android-client` + +- Role: client-only Android package. +- Allowed dependencies: `@use-voltra/android`, `react`, `expo`, `react-native`. +- Contains the native bridge, RN preview components, events, preload APIs, widget client APIs, live update client APIs, and client-only public types. + +### `@use-voltra/ios-server` + +- Allowed dependencies: `@use-voltra/core`, `@use-voltra/server`, `@use-voltra/ios`, `react`. +- Forbidden dependencies: `expo`, `react-native`, `@use-voltra/ios-client`. + +### `@use-voltra/android-server` + +- Allowed dependencies: `@use-voltra/core`, `@use-voltra/server`, `@use-voltra/android`, `react`. +- Forbidden dependencies: `expo`, `react-native`, `@use-voltra/android-client`. + +### `voltra` + +- Role: compatibility facade package. +- May depend on all public Voltra packages. +- Must preserve the current user-facing export surface. +- Must not add new root or subpath exports in this refactor. +- Expo Module code stays here for now. diff --git a/packages/android-client/package.json b/packages/android-client/package.json new file mode 100644 index 00000000..c9382d8d --- /dev/null +++ b/packages/android-client/package.json @@ -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": "*" + } +} diff --git a/packages/android/src/VoltraModule.ts b/packages/android-client/src/VoltraModule.ts similarity index 100% rename from packages/android/src/VoltraModule.ts rename to packages/android-client/src/VoltraModule.ts diff --git a/packages/android/src/components/VoltraView.tsx b/packages/android-client/src/components/VoltraView.tsx similarity index 53% rename from packages/android/src/components/VoltraView.tsx rename to packages/android-client/src/components/VoltraView.tsx index f951b073..0e49b361 100644 --- a/packages/android/src/components/VoltraView.tsx +++ b/packages/android-client/src/components/VoltraView.tsx @@ -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 - /** - * 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', diff --git a/packages/android/src/components/VoltraWidgetPreview.tsx b/packages/android-client/src/components/VoltraWidgetPreview.tsx similarity index 73% rename from packages/android/src/components/VoltraWidgetPreview.tsx rename to packages/android-client/src/components/VoltraWidgetPreview.tsx index 54baf59d..7f5760d9 100644 --- a/packages/android/src/components/VoltraWidgetPreview.tsx +++ b/packages/android-client/src/components/VoltraWidgetPreview.tsx @@ -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 = { @@ -18,19 +15,10 @@ const WIDGET_DIMENSIONS: Record & { - /** - * Android widget size to preview - */ family: AndroidWidgetFamily - /** - * Additional styles to apply on top of the widget dimensions - */ style?: StyleProp } -/** - * 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 = [ diff --git a/packages/ios/src/events.ts b/packages/android-client/src/events.ts similarity index 70% rename from packages/ios/src/events.ts rename to packages/android-client/src/events.ts index eb84787f..db0f7c93 100644 --- a/packages/ios/src/events.ts +++ b/packages/android-client/src/events.ts @@ -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( event: K, listener: (event: VoltraEventMap[K]) => void diff --git a/packages/android/src/client.ts b/packages/android-client/src/index.ts similarity index 86% rename from packages/android/src/client.ts rename to packages/android-client/src/index.ts index a63f5374..3ce46084 100644 --- a/packages/android/src/client.ts +++ b/packages/android-client/src/index.ts @@ -1,4 +1,3 @@ -// Android Live Update API and types export { endAllAndroidLiveUpdates, isAndroidLiveUpdateActive, @@ -16,8 +15,6 @@ export type { UseAndroidLiveUpdateOptions, UseAndroidLiveUpdateResult, } from './live-update/types.js' - -// Android Widget API and types export { clearAllAndroidWidgets, clearAndroidWidget, @@ -32,19 +29,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, diff --git a/packages/android-client/src/live-update/api.ts b/packages/android-client/src/live-update/api.ts new file mode 100644 index 00000000..ad9c77d6 --- /dev/null +++ b/packages/android-client/src/live-update/api.ts @@ -0,0 +1,125 @@ +import { useCallback, useEffect, useRef, useState } from 'react' + +import { renderAndroidLiveUpdateToString, type AndroidLiveUpdateVariants } from '@use-voltra/android' + +import VoltraModule from '../VoltraModule.js' +import { useUpdateOnHMR } from '../utils/index.js' +import type { + StartAndroidLiveUpdateOptions, + UpdateAndroidLiveUpdateOptions, + UseAndroidLiveUpdateOptions, + UseAndroidLiveUpdateResult, +} from './types.js' + +export const useAndroidLiveUpdate = ( + variants: AndroidLiveUpdateVariants, + options?: UseAndroidLiveUpdateOptions +): UseAndroidLiveUpdateResult => { + const [targetId, setTargetId] = useState(() => { + if (options?.updateName) { + return isAndroidLiveUpdateActive(options.updateName) ? options.updateName : null + } + return null + }) + + const isActive = targetId !== null + const optionsRef = useRef(options) + const variantsRef = useRef(variants) + const lastUpdateOptionsRef = useRef(undefined) + + useEffect(() => { + optionsRef.current = options + }, [options]) + + useEffect(() => { + variantsRef.current = variants + }, [variants]) + + useUpdateOnHMR() + + const start = useCallback(async (options?: StartAndroidLiveUpdateOptions) => { + const id = await startAndroidLiveUpdate(variantsRef.current, { + ...optionsRef.current, + ...options, + }) + setTargetId(id) + }, []) + + const update = useCallback( + async (options?: UpdateAndroidLiveUpdateOptions) => { + if (!targetId) { + return + } + + const updateOptions = { ...optionsRef.current, ...options } + lastUpdateOptionsRef.current = updateOptions + await updateAndroidLiveUpdate(targetId, variantsRef.current, updateOptions) + }, + [targetId] + ) + + const end = useCallback(async () => { + if (!targetId) { + return + } + + await stopAndroidLiveUpdate(targetId) + setTargetId(null) + }, [targetId]) + + useEffect(() => { + if (!options?.autoStart) { + return + } + + start() + }, [options?.autoStart, start]) + + useEffect(() => { + if (!options?.autoUpdate) return + update(lastUpdateOptionsRef.current) + }, [options?.autoUpdate, update, variants]) + + return { + start, + update, + end, + isActive, + } +} + +export const startAndroidLiveUpdate = async ( + variants: AndroidLiveUpdateVariants, + options?: StartAndroidLiveUpdateOptions +): Promise => { + const payload = renderAndroidLiveUpdateToString(variants) + + const notificationId = await VoltraModule.startAndroidLiveUpdate(payload, { + updateName: options?.updateName, + channelId: options?.channelId || variants.channelId || 'voltra_live_updates', + }) + + return notificationId +} + +export const updateAndroidLiveUpdate = async ( + notificationId: string, + variants: AndroidLiveUpdateVariants, + _options?: UpdateAndroidLiveUpdateOptions +): Promise => { + const payload = renderAndroidLiveUpdateToString(variants) + + return VoltraModule.updateAndroidLiveUpdate(notificationId, payload) +} + +export const stopAndroidLiveUpdate = async (notificationId: string): Promise => { + return VoltraModule.stopAndroidLiveUpdate(notificationId) +} + +export const isAndroidLiveUpdateActive = (updateName: string): boolean => { + return VoltraModule.isAndroidLiveUpdateActive(updateName) +} + +export async function endAllAndroidLiveUpdates(): Promise { + return VoltraModule.endAllAndroidLiveUpdates() +} diff --git a/packages/android-client/src/live-update/types.ts b/packages/android-client/src/live-update/types.ts new file mode 100644 index 00000000..106d3877 --- /dev/null +++ b/packages/android-client/src/live-update/types.ts @@ -0,0 +1,9 @@ +export type { + AndroidLiveUpdateJson, + AndroidLiveUpdateVariants, + AndroidLiveUpdateVariantsJson, + StartAndroidLiveUpdateOptions, + UpdateAndroidLiveUpdateOptions, + UseAndroidLiveUpdateOptions, + UseAndroidLiveUpdateResult, +} from '@use-voltra/android' diff --git a/packages/android-client/src/preload.ts b/packages/android-client/src/preload.ts new file mode 100644 index 00000000..bce01ce0 --- /dev/null +++ b/packages/android-client/src/preload.ts @@ -0,0 +1,32 @@ +import type { PreloadImageOptions, PreloadImagesResult } from './types.js' +import VoltraModule from './VoltraModule.js' + +export async function preloadImages(images: PreloadImageOptions[]): Promise { + try { + return await VoltraModule.preloadImages(images) + } catch (error) { + return { + succeeded: [], + failed: images.map((img) => ({ + key: img.key, + error: error instanceof Error ? error.message : 'Unknown error', + })), + } + } +} + +export async function clearPreloadedImages(keys?: string[]): Promise { + try { + await VoltraModule.clearPreloadedImages(keys ?? null) + } catch (error) { + console.error('Failed to clear preloaded images:', error) + } +} + +export async function reloadWidgets(widgetIds?: string[]): Promise { + try { + await VoltraModule.reloadAndroidWidgets(widgetIds ?? null) + } catch (error) { + console.error('Failed to reload Android widgets:', error) + } +} diff --git a/packages/android-client/src/types.ts b/packages/android-client/src/types.ts new file mode 100644 index 00000000..9b83ed51 --- /dev/null +++ b/packages/android-client/src/types.ts @@ -0,0 +1,23 @@ +export type { + AndroidLiveUpdateJson, + AndroidLiveUpdateVariants, + AndroidLiveUpdateVariantsJson, + AndroidWidgetSize, + AndroidWidgetSizeVariant, + AndroidWidgetVariants, + EventSubscription, + PreloadImageFailure, + PreloadImageOptions, + PreloadImagesResult, + StartAndroidLiveUpdateOptions, + UpdateAndroidLiveUpdateOptions, + UpdateAndroidWidgetOptions, + UseAndroidLiveUpdateOptions, + UseAndroidLiveUpdateResult, + VoltraElementJson, + VoltraElementRef, + VoltraNodeJson, + VoltraPropValue, + WidgetInfo, + WidgetServerCredentials, +} from '@use-voltra/android' diff --git a/packages/android-client/src/utils/index.ts b/packages/android-client/src/utils/index.ts new file mode 100644 index 00000000..49f45673 --- /dev/null +++ b/packages/android-client/src/utils/index.ts @@ -0,0 +1 @@ +export * from './useUpdateOnHMR.js' diff --git a/packages/android/src/utils/useUpdateOnHMR.ts b/packages/android-client/src/utils/useUpdateOnHMR.ts similarity index 64% rename from packages/android/src/utils/useUpdateOnHMR.ts rename to packages/android-client/src/utils/useUpdateOnHMR.ts index 74c86edb..2f04f968 100644 --- a/packages/android/src/utils/useUpdateOnHMR.ts +++ b/packages/android-client/src/utils/useUpdateOnHMR.ts @@ -1,14 +1,9 @@ import { useEffect, useState } from 'react' declare global { - // HMR accept function var __accept: (...args: unknown[]) => void } -/** - * Taps into the HMR accept function to forcefully re-render the component when any module is updated. - * Is only available in development mode. - */ export const useUpdateOnHMR = () => { const [, forceUpdate] = useState(0) @@ -17,7 +12,6 @@ export const useUpdateOnHMR = () => { return } - // Override the accept function to forcefully re-render when any module is updated const oldAccept = global['__accept'] global['__accept'] = (...args) => { forceUpdate((prev) => prev + 1) diff --git a/packages/android-client/src/widgets/api.ts b/packages/android-client/src/widgets/api.ts new file mode 100644 index 00000000..5118743e --- /dev/null +++ b/packages/android-client/src/widgets/api.ts @@ -0,0 +1,51 @@ +import { + renderAndroidWidgetToString, + type AndroidWidgetVariants, + type UpdateAndroidWidgetOptions, + type WidgetInfo, +} from '@use-voltra/android' + +import VoltraModule from '../VoltraModule.js' + +export type { + AndroidWidgetSize, + AndroidWidgetSizeVariant, + AndroidWidgetVariants, + UpdateAndroidWidgetOptions, + WidgetInfo, +} from '@use-voltra/android' + +export const updateAndroidWidget = async ( + widgetId: string, + variants: AndroidWidgetVariants, + options?: UpdateAndroidWidgetOptions +): Promise => { + const payload = renderAndroidWidgetToString(variants) + + return VoltraModule.updateAndroidWidget(widgetId, payload, { + deepLinkUrl: options?.deepLinkUrl, + }) +} + +export const reloadAndroidWidgets = async (widgetIds?: string[]): Promise => { + return VoltraModule.reloadAndroidWidgets(widgetIds ?? null) +} + +export const clearAndroidWidget = async (widgetId: string): Promise => { + return VoltraModule.clearAndroidWidget(widgetId) +} + +export const clearAllAndroidWidgets = async (): Promise => { + return VoltraModule.clearAllAndroidWidgets() +} + +export const requestPinAndroidWidget = async ( + widgetId: string, + options?: { previewWidth?: number; previewHeight?: number } +): Promise => { + return VoltraModule.requestPinGlanceAppWidget(widgetId, options) +} + +export const getActiveWidgets = async (): Promise => { + return VoltraModule.getActiveWidgets() +} diff --git a/packages/android/src/widgets/server-credentials.ts b/packages/android-client/src/widgets/server-credentials.ts similarity index 53% rename from packages/android/src/widgets/server-credentials.ts rename to packages/android-client/src/widgets/server-credentials.ts index c8313712..07a35f25 100644 --- a/packages/android/src/widgets/server-credentials.ts +++ b/packages/android-client/src/widgets/server-credentials.ts @@ -1,17 +1,8 @@ import type { WidgetServerCredentials } from '../types.js' import VoltraModule from '../VoltraModule.js' -// Re-export types for public API export type { WidgetServerCredentials } from '../types.js' -/** - * Store server credentials for Android widget server-driven updates. - * - * Credentials are stored in DataStore, which is - * automatically accessible by the widget's WorkManager background worker. - * - * @param credentials - The server credentials to store - */ export async function setWidgetServerCredentials(credentials: WidgetServerCredentials): Promise { if (!credentials.token) { throw new Error('[Voltra] [Android] setWidgetServerCredentials: token is required') @@ -19,11 +10,6 @@ export async function setWidgetServerCredentials(credentials: WidgetServerCreden return VoltraModule.setWidgetServerCredentials(credentials) } -/** - * Clear stored server credentials for Android widget updates. - * All widgets are automatically reloaded after clearing credentials so they - * revert to their default/unauthenticated state. - */ export async function clearWidgetServerCredentials(): Promise { return VoltraModule.clearWidgetServerCredentials() } diff --git a/packages/android-client/tsconfig.base.json b/packages/android-client/tsconfig.base.json new file mode 100644 index 00000000..49f9c30e --- /dev/null +++ b/packages/android-client/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "rootDir": "./src", + "moduleResolution": "node", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"] +} diff --git a/packages/android-client/tsconfig.cjs.json b/packages/android-client/tsconfig.cjs.json new file mode 100644 index 00000000..a6b3ca9c --- /dev/null +++ b/packages/android-client/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "outDir": "./build/cjs", + "declaration": false, + "sourceMap": true + } +} diff --git a/packages/android-client/tsconfig.esm.json b/packages/android-client/tsconfig.esm.json new file mode 100644 index 00000000..2bb18d33 --- /dev/null +++ b/packages/android-client/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "ES2020", + "outDir": "./build/esm", + "declaration": false, + "sourceMap": true + } +} diff --git a/packages/android-client/tsconfig.typecheck.json b/packages/android-client/tsconfig.typecheck.json new file mode 100644 index 00000000..477960f6 --- /dev/null +++ b/packages/android-client/tsconfig.typecheck.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "ES2020", + "rootDir": "../..", + "baseUrl": "../..", + "paths": { + "voltra": ["packages/voltra/src/index.ts"], + "voltra/*": ["packages/voltra/src/*"], + "@use-voltra/android": ["packages/android/src/index.ts"], + "@use-voltra/android/client": ["packages/android-client/src/index.ts"], + "@use-voltra/android-server": ["packages/android-server/src/index.ts"], + "@use-voltra/core": ["packages/core/src/index.ts"], + "@use-voltra/ios": ["packages/ios/src/index.ts"], + "@use-voltra/ios/client": ["packages/ios-client/src/index.ts"], + "@use-voltra/ios-server": ["packages/ios-server/src/index.ts"], + "@use-voltra/server": ["packages/server/src/index.ts"] + } + } +} diff --git a/packages/android-client/tsconfig.types.json b/packages/android-client/tsconfig.types.json new file mode 100644 index 00000000..8ec821ee --- /dev/null +++ b/packages/android-client/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "ES2020", + "outDir": "./build/types", + "declaration": true, + "emitDeclarationOnly": true, + "declarationMap": true + } +} diff --git a/packages/android-server/tsconfig.typecheck.json b/packages/android-server/tsconfig.typecheck.json index d5d9364f..0de799cd 100644 --- a/packages/android-server/tsconfig.typecheck.json +++ b/packages/android-server/tsconfig.typecheck.json @@ -8,13 +8,15 @@ "voltra": ["packages/voltra/src/index.ts"], "voltra/*": ["packages/voltra/src/*"], "@use-voltra/android": ["packages/android/src/index.ts"], - "@use-voltra/android/client": ["packages/android/src/client.ts"], + "@use-voltra/android-client": ["packages/android-client/src/index.ts"], + "@use-voltra/android/client": ["packages/android-client/src/index.ts"], "@use-voltra/android/internal": ["packages/android/src/internal.ts"], "@use-voltra/android/server": ["packages/android/src/server.ts"], "@use-voltra/android-server": ["packages/android-server/src/index.ts"], "@use-voltra/core": ["packages/core/src/index.ts"], "@use-voltra/ios": ["packages/ios/src/index.ts"], - "@use-voltra/ios/client": ["packages/ios/src/client.ts"], + "@use-voltra/ios-client": ["packages/ios-client/src/index.ts"], + "@use-voltra/ios/client": ["packages/ios-client/src/index.ts"], "@use-voltra/ios/server": ["packages/ios/src/server.ts"], "@use-voltra/ios-server": ["packages/ios-server/src/index.ts"], "@use-voltra/server": ["packages/server/src/index.ts"] diff --git a/packages/android/package.json b/packages/android/package.json index 3d838fc2..f0715ea5 100644 --- a/packages/android/package.json +++ b/packages/android/package.json @@ -12,12 +12,6 @@ "import": "./build/esm/index.js", "default": "./build/esm/index.js" }, - "./client": { - "types": "./build/types/client.d.ts", - "require": "./build/cjs/client.js", - "import": "./build/esm/client.js", - "default": "./build/esm/client.js" - }, "./server": { "types": "./build/types/server.d.ts", "require": "./build/cjs/server.js", @@ -64,7 +58,6 @@ "license": "MIT", "homepage": "https://use-voltra.dev", "peerDependencies": { - "expo": "*", "react": "*", "react-native": "*" } diff --git a/packages/android/src/index.ts b/packages/android/src/index.ts index 70dcfee0..11634e3c 100644 --- a/packages/android/src/index.ts +++ b/packages/android/src/index.ts @@ -1,6 +1,8 @@ // Android component namespace export * as VoltraAndroid from './jsx/primitives.js' export { AndroidDynamicColors } from './dynamic-colors.js' +export { renderAndroidLiveUpdateToJson, renderAndroidLiveUpdateToString } from './live-update/renderer.js' +export { renderAndroidViewToJson, renderAndroidWidgetToJson, renderAndroidWidgetToString } from './widgets/renderer.js' // Android types export type { VoltraAndroidBaseProps } from './jsx/baseProps.js' @@ -11,6 +13,33 @@ export type { VoltraAndroidViewStyle, } from './styles/types.js' export type { AndroidColorValue, AndroidDynamicColorRole, AndroidDynamicColorToken } from './dynamic-colors.js' +export type { + AndroidLiveUpdateJson, + AndroidLiveUpdateVariants, + AndroidLiveUpdateVariantsJson, + StartAndroidLiveUpdateOptions, + UpdateAndroidLiveUpdateOptions, + UseAndroidLiveUpdateOptions, + UseAndroidLiveUpdateResult, +} from './live-update/types.js' +export type { + EventSubscription, + PreloadImageFailure, + PreloadImageOptions, + PreloadImagesResult, + VoltraElementJson, + VoltraElementRef, + VoltraNodeJson, + VoltraPropValue, + WidgetServerCredentials, +} from './types.js' +export type { + AndroidWidgetSize, + AndroidWidgetSizeVariant, + AndroidWidgetVariants, + UpdateAndroidWidgetOptions, + WidgetInfo, +} from './widgets/types.js' // Component prop types export type { BoxProps } from './jsx/Box.js' diff --git a/packages/android/src/live-update/api.ts b/packages/android/src/live-update/api.ts deleted file mode 100644 index 3a80065a..00000000 --- a/packages/android/src/live-update/api.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react' - -import { useUpdateOnHMR } from '../utils/index.js' -import VoltraModule from '../VoltraModule.js' -import { renderAndroidLiveUpdateToString } from './renderer.js' -import type { - AndroidLiveUpdateVariants, - StartAndroidLiveUpdateOptions, - UpdateAndroidLiveUpdateOptions, - UseAndroidLiveUpdateOptions, - UseAndroidLiveUpdateResult, -} from './types.js' - -/** - * @unstable This API is experimental and may change in future versions. - * - * React hook for managing Android Live Updates with automatic lifecycle handling. - * - * @param variants - The Android Live Update content variants to display - * @param options - Configuration options for the hook - * @returns Object with start, update, end methods and isActive state - * - * @example - * ```tsx - * import { VoltraAndroid, useAndroidLiveUpdate } from 'voltra' - * - * const MyLiveUpdate = () => { - * const { start, update, end, isActive } = useAndroidLiveUpdate({ - * collapsed: Delivery arriving, - * expanded: ..., - * channelId: 'delivery_updates', - * }, { - * updateName: 'my-live-update', - * autoStart: true, - * autoUpdate: true - * }) - * - * return ( - * - *