Skip to content

feat: Channel type, translation flags, transcriptions, and stop incoming emergency#29

Draft
adambailey- wants to merge 46 commits into
developfrom
feature/native-sdk-parity
Draft

feat: Channel type, translation flags, transcriptions, and stop incoming emergency#29
adambailey- wants to merge 46 commits into
developfrom
feature/native-sdk-parity

Conversation

@adambailey-

Copy link
Copy Markdown
Member

Summary

Ports four native SDK features to the React Native SDK for parity with the Android and iOS embeddable SDKs:

Feature Android PR iOS PR
Channel type (ZelloChannel.channelType / ZelloChannelType: dynamic, dispatch, team, groupConversation) zello-android-client#9773 zello-ios-client#10976
Translation flags (ZelloChannel.translationsEnabled, ZelloIncomingVoiceMessage.isTranslation) zello-android-client#9696 included in zello-ios-client#10861
Voice message transcriptions (ZelloHistoryVoiceMessage.transcription, ZelloTranscription / ZelloTranslation, new ZelloEvent.HISTORY_VOICE_MESSAGE_TRANSCRIPTION_AVAILABLE event) zello-android-client#9686 zello-ios-client#10861
Stop incoming emergency (Zello.stopIncomingEmergency(), allowEmergencyEndOwn / allowEmergencyEndOthers on ZelloChannelOptions) zello-android-client#9685 zello-ios-client#10860

Also wires the example app: transcription text renders in the history dialog (and refreshes when a transcription arrives asynchronously), and channels show a "Stop Incoming Emergency" context menu option gated on allowEmergencyEndOthers plus an active incoming emergency, mirroring the native example apps.

Blocked by

Notes

  • Channel type strings on the bridge use the Android enum names (DYNAMIC, DISPATCH, TEAM, GROUP_CONVERSATION); the iOS module maps its enum to the same strings, and group conversations are normalized to GROUP_CONVERSATION on both platforms.
  • ZelloTranscription.text / language are optional on the TS side to match Android nullability (iOS always provides them).
  • TS typecheck and lint pass for both the library and the example app. Native modules cannot compile until the native SDK releases above are published.

🤖 Generated with Claude Code

zello-n and others added 30 commits June 17, 2025 17:08
Increase Android SDK to 1.0.+ (1.0.4)
The example app pins iOS to UIUserInterfaceStyle=Light but Android
used Theme.AppCompat.DayNight, so on Android in dark mode the
hardcoded white modal dialogs rendered with the system white text,
making content invisible. Match iOS by using the Light theme.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a pod install-time deprecation warning to the podspec and a README notice. CocoaPods still works and remains supported; Swift Package Manager is the recommended installation path going forward.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Upgrade the library and example app from RN 0.74.5 to 0.86.0 (React 19.2.3).

JS/tooling:
- Bump RN, React, @react-native/* tooling, builder-bob 0.42, TypeScript 5.8
- Require Node >= 22.11 (.nvmrc -> v22)
- Switch example SDK dep from file:.. to link:.. (avoids yarn pack OOM)
- Map scoped package name to ./src in tsconfig paths
- Migrate jest preset to @react-native/jest-preset
- Fix eslint via plugin:prettier/recommended (0.86 config dropped the plugin)
- Use requestNotifications() on Android (rn-permissions v5 dropped POST_NOTIFICATIONS)
- Bump example deps (navigation 7, screens 4, safe-area-context 5, firebase 24, etc.)

Android (config only, not build-verified - no local JDK/SDK):
- SDK 36, buildTools 36.0.0, NDK 27.1.12297006, Kotlin 2.1.20, Gradle 9.3.1
- AGP 8.13.0, Hilt/Dagger 2.56.2, newArchEnabled=true

Verified: typecheck, lint, bob build, and Metro JS bundle all pass.
Native builds pending the TurboModule conversion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a single codegen spec (src/specs/NativeZelloSdk.ts) that both
platforms will implement, replacing the two divergent legacy bridge modules.

- configure takes one object param; getHistory/getHistoryImageData are Promises
- events stay off the spec, emitted on the global DeviceEventEmitter 'zellosdk' channel
- wire codegenConfig (RNZelloSdkSpec, outputDir ios/android); add @react-native-community/cli
- mark create-react-native-library type as turbo-module; gitignore generated output

Validated: 'yarn bob build --target codegen' generates the iOS NativeZelloSdkSpec
protocol and Android NativeZelloSdkSpec.java. Native impls and JS rewiring follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the iOS TurboModule conforming to the generated NativeZelloSdkSpec:
- ZelloSdkModule.{h,mm}: ObjC++ module, RCT_EXPORT_MODULE(NativeZelloSdk),
  getTurboModule -> NativeZelloSdkSpecJSI, forwards all 29 methods to Swift
- ZelloSdkModuleImpl.swift: Swift impl calling Zello.shared; configure takes
  an object param, getHistory/getHistoryImageData use resolve/reject
- The existing ZelloIOSSdkModule RCTEventEmitter is left intact for events
  (both share the Zello.shared singleton, no event-layer rewrite needed)
- Fix create-react-native-library type to module-new

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generated NativeZelloSdkSpec header is Obj-C++ only, but ZelloSdkModule.h
is pulled into the pod's Swift umbrella. Move the spec import and protocol
conformance into ZelloSdkModule.mm via a class extension, leaving the public
header free of C++. Also exclude ios/generated (local bob codegen output) from
the podspec so it is never compiled by the host app.

Verified: the react-native-zello-sdk pod and both new files now compile clean
in the example iOS build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace per-platform NativeModules method calls in src/sdk/index.ts with the
unified NativeZelloSdk TurboModule: collapse ~25 Platform.OS branches to single
calls, pass the whole config object to configure, and use the returned Promise
for getHistory/getHistoryImageData. ZelloAndroidSdkModule/ZelloIOSSdkModule are
kept only for the event emitter.

Verified: typecheck, lint, and Metro bundle pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Import the pod's Swift header framework-style (use_frameworks!) via __has_include
- Drop BUILD_LIBRARY_FOR_DISTRIBUTION=YES from the Podfile post_install: it forced
  Swift module-interface verification that broke RNScreens under use_frameworks!
- Regenerate Podfile.lock / project for RN 0.86 pods (prebuilt core, Hermes, SPM
  integration); gitignore the machine-local .xcode.env.local

Verified: 'xcodebuild ... -sdk iphonesimulator build' => BUILD SUCCEEDED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Under the bridgeless New Architecture, RCTEventEmitter's sendEvent/receiveEvent
path is not callable. Emit the unified 'zellosdk' event through the global
RCTDeviceEventEmitter via the injected RCTCallableJSModules (matching how the
Android module already emits), and subscribe on the JS side with
DeviceEventEmitter instead of new NativeEventEmitter(module).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Gemfile: allow CocoaPods 1.16 (drop the <1.15 cap); add nkf + tsort, which
  Ruby 3.4+/4.0 removed from stdlib but CocoaPods' CFPropertyList needs.
- Podfile: build ZelloSDK's Swift deps (PhoneNumberKit, SnowplowTracker,
  CocoaLumberjack, PromisesSwift) with BUILD_LIBRARY_FOR_DISTRIBUTION so the
  prebuilt ZelloSDK can resolve their resilient symbols at launch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Firebase was declared but never used in the example: no JS import, no AppDelegate
configuration, no GoogleService-Info.plist, and the NotificationServiceExtension
doesn't reference it. Removing it trims the dependency surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a ZELLO_USE_SPM=1 opt-in to the podspec: when set, ZelloSDK is pulled via
Swift Package Manager (github.com/zelloptt/ios-mobile-sdk, product ZelloSDKUmbrella,
up-to-next-major from 2.0.0) using RN's spm_dependency helper, instead of the
CocoaPods 'pod ZelloSDK'. CocoaPods stays the default.

Full CocoaPods removal isn't possible on RN 0.86 (RN itself still requires
CocoaPods; SPM-only is an unshipped RN RFC, react-native-community/discussions-and-proposals#994),
so this mirrors sentry-react-native's SENTRY_USE_SPM approach: SPM for the native
SDK, CocoaPods for the RN layer.

Verified: ZELLO_USE_SPM=1 pod install + xcodebuild resolves ZelloSDK 2.0.5 from SPM
and the example builds, launches, and the SDK initializes - with no resilient-symbol
dyld workaround needed (SPM binaries are prebuilt resiliently).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README: New Architecture + Node prerequisites; Turbo Native Module intro;
  native ZelloSDK via CocoaPods (default) or SPM (ZELLO_USE_SPM=1, Sentry-style);
  the required BUILD_LIBRARY_FOR_DISTRIBUTION post_install for the CocoaPods path
  on Xcode 16/26+; and why full CocoaPods removal isn't possible on RN 0.86 yet.
- CHANGELOG: 3.0.0 entry (New Arch breaking change, SPM opt-in).
- Bump version 2.0.1 -> 3.0.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rboModule

- Extend the generated NativeZelloSdkSpec; drop @ReactMethod and the getName override.
- Unify signatures to the shared codegen spec: configure(ReadableMap), selectContact
  (was setSelectedContact), getHistoryImageData (was getImageDataForHistoryImageMessage),
  split connectGroupConversation/disconnectGroupConversation; getHistory and
  getHistoryImageData resolve a Promise instead of invoking a Callback.
- JS getSdk(): on Android the NativeZelloSdk TurboModule owns the Zello delegate;
  drop the now-stale ZelloAndroidSdkModule NativeModules reference.
- Update the manual Hilt ReactPackage (README + example) to BaseReactPackage with a
  ReactModuleInfoProvider (isTurboModule = true).
- eslintIgnore: skip build/, generated/, Pods/ dirs (SPM checkouts were being linted).

Not built locally (no Android SDK/JDK) - to be verified in CI. connectGroupConversation
assumes the Android SDK's connectChannel accepts a ZelloGroupConversation (flagged in code).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Trigger on master (was main — CI never ran on this repo's default branch).
- build-ios: matrix over [cocoapods, spm] to verify both native-ZelloSDK install
  paths; install pods via bundle so the updated Gemfile (CocoaPods 1.16 + nkf/tsort)
  is honored, with RCT_NEW_ARCH_ENABLED=1; ZELLO_USE_SPM=1 for the spm variant.
- Add jest unit tests for the binary-search/comparator helpers (the test job had
  zero tests and was failing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI surfaced these (Android can't be built locally — no SDK/JDK here):
- settings.gradle: the removed native_modules.gradle apply -> com.facebook.react.settings
  plugin + autolinkLibrariesFromCommand().
- app/build.gradle: drop applyNativeModulesAppBuildGradle; use autolinkLibrariesWithApp()
  inside the react {} block.
- MainApplication: SoLoader.init(this, OpenSourceMergedSoMapping) (the boolean overload is
  gone in RN 0.76+).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Hilt Gradle plugin was bumped to 2.56.2 (Kotlin 2.1.20 compat) but the example
app still pinned hilt-android / hilt-android-compiler 2.51. Dagger requires the
plugin and runtime libs to match, so the build would fail. Bumped both to 2.56.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RN 0.86 requires Xcode >= 16.1; the macos-14 runner ships 15.4, which made
use_react_native! abort with 'Please upgrade XCode'. macos-15 defaults to Xcode 16.x.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
compileDebugKotlin couldn't resolve NativeZelloSdkSpec because the library's
build.gradle was still the module-legacy structure. Add the module-new pieces:
- sourceSets generated/java + generated/jni so the generated spec compiles
- react { jsRootDir, libraryName = NativeZelloSdk, codegenJavaPackageName } so the
  RN Gradle plugin generates the library codegen into that location.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The library uses kapt (Hilt); the RN Gradle plugin adds build/generated/source/codegen/java
via finalizeDsl, which the Kotlin/kapt compile doesn't reliably pick up, leaving
NativeZelloSdkSpec unresolved. Register the codegen output dir on the main source set
explicitly (replacing the wrong generated/java path). The other Kotlin libs (screens,
safe-area-context) compile their codegen fine because they don't use kapt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…piles

The library's only @Inject was on ZelloAndroidSdkModule, which the host app
constructs manually (ZelloAndroidSdkPackage) — Hilt never processed it, so kapt was
dead weight. With kapt enabled, the generated NativeZelloSdkSpec wasn't visible to
kaptDebugKotlin/compileDebugKotlin, failing the build. Removing the Hilt plugin, kapt,
the Hilt deps, and the vestigial @Inject lets the codegen spec compile like the other
kapt-free Kotlin libs (screens, safe-area-context). The example app keeps Hilt (where
Zello is actually injected and passed to the package).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rxmichael and others added 16 commits June 16, 2026 23:12
…osdk

The gradle plugin passes --javaPackageName com.zellosdk (from
codegenConfig.android.javaPackageName), so RN generates the TurboModule spec as
com.zellosdk.NativeZelloSdkSpec, not com.facebook.fbreact.specs.NativeZelloSdkSpec.
Verified by running generate-specs-cli locally with that flag. Since the module is
already in package com.zellosdk, the spec is same-package and needs no import; the
old import was the real 'Unresolved reference NativeZelloSdkSpec' cause (kapt was a
red herring). All 29 override signatures match the generated abstract methods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The example app linked com.zello:sdk:1.0.4, whose POM declares hilt-android:2.52
(pre-2.55, so it never emits Dagger's LazyClassKey helpers). The app was on Hilt
2.56.2, which generates SingletonC code importing *_LazyMapKey classes that don't
exist in the AAR -> :app:hiltJavaCompileDebug failed with 78 'cannot find symbol'
errors. Pinned the app + gradle-plugin classpath to 2.52 to match the AAR exactly.
(The library itself no longer uses Hilt, so it no longer constrains this version.)
Verified the AAR's Hilt version by fetching its POM from the S3 maven.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hilt 2.52 matched the com.zello:sdk AAR but crashed :app:kaptDebugKotlin under
Kotlin 2.1.20 (2.52 predates Kotlin 2.1). 2.54 is the newest release before
Dagger's LazyClassKey became the default (2.55), so it both runs cleanly under
Kotlin-2.1 kapt and avoids the _LazyMapKey references the pre-2.55 AAR lacks.
Also set kapt.verbose=true so any remaining annotation-processor error surfaces
in the CI log instead of a generic worker failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Zello SDK

The com.zello AARs are built with Dagger 2.52, whose @LazyClassKey impl predates
the per-binding *_LazyMapKey proxy classes. The example app must use Dagger >= 2.54
(RN 0.86 forces Kotlin 2.1.20; Dagger 2.52 crashes kapt under it), and >= 2.53
consumes the SDK's @LazyClassKey ViewModel bindings by referencing those proxies
from DaggerMainApplication_HiltComponents_SingletonC -> :app:hiltJavaCompileDebug
failed with 78 'cannot find symbol ... _LazyMapKey' errors.

Generate the 78 missing keeper classes (39 ViewModels x BindsModule_Binds +
KeyModule_Provide), matching Dagger's LazyMapKeyProxyGenerator output, by scanning
the SDK AARs for *_HiltModules classes. Workaround for a stale prebuilt SDK; remove
once the Zello Android SDK ships a Dagger >= 2.53 build (which emits these itself).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ncoming emergency

Ports the following native SDK features to the React Native SDK:
- Channel type (ZelloChannel.channelType / ZelloChannelType) - ANDROID-3816 / IOS-4385
- Translation flags (ZelloChannel.translationsEnabled, ZelloIncomingVoiceMessage.isTranslation) - ANDROID-3706
- Voice message transcriptions (ZelloHistoryVoiceMessage.transcription, ZelloTranscription,
  HISTORY_VOICE_MESSAGE_TRANSCRIPTION_AVAILABLE event) - ANDROID-3685 / IOS-4252
- Stop incoming emergency (Zello.stopIncomingEmergency, allowEmergencyEndOwn/Others
  channel options) - ANDROID-3683 / IOS-4251

Bumps native dependencies: com.zello:sdk to 2.+ and ZelloSDK pod to ~> 3.0.
Wires the example app: transcription text in the history dialog and a
"Stop Incoming Emergency" context menu option.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ectChannel

Verified against com.zello:sdk:1.0.4 bytecode: ZelloGroupConversation extends
ZelloChannel and Zello.connectChannel/disconnectChannel take a ZelloChannel, so
passing a group conversation is a valid upcast. Resolves the prior TODO note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The TurboModule boundary stays NSDictionary (the codegen spec uses UnsafeObject
because configure() is platform-specific and iOS only reads the 'ios' slice), but
the untyped key lookups are now centralized in a typed private Config struct so the
method body is type-safe and the payload-shape assumptions live in one place.
Behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Confirmed against the ZelloSDK.xcframework swiftinterface: Zello.configuration is a
settable property and there is no configure() method — configuration is applied by
assignment. Master (and the migrated code) built a ZelloConfiguration with the
appGroup and pushNotificationEnvironment but never assigned it, so those settings
were silently ignored on iOS (push env always defaulted to .production, app group
never set). Assign zello.configuration so configure() takes effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The root npm lockfile was stale from before the migration (pinned react-native
0.74.5 / react 18.2.0). Regenerated with 'npm install --package-lock-only' so it
matches the current package.json and yarn.lock resolution: react-native 0.86.0,
react 19.2.3, @react-native/jest-preset 0.86.0, no 0.74.x references. The example
workspace's SDK dep is recorded as file:.. (npm's local-symlink form; the
package.json keeps yarn's link:.. and is unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Podfile.lock was committed stale at 2.0.1 while the podspec is 3.0.0, so
:app:CheckPodsManifest failed the iOS build with 'sandbox not in sync'. Updated the
lock to match the installed Pods sandbox (Manifest.lock, 3.0.0). Verified: the
example app builds and launches on the iOS simulator afterward.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The create/add-users dialogs used react-native-paper's adaptive Checkbox, which
renders Checkbox.IOS on iOS -- that variant shows only a checkmark when checked and
nothing (no box outline) when unchecked, so the rows looked empty on the simulator.
Use Checkbox.Android explicitly, which draws a visible box in both states on every
platform.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the create/add-users group-conversation dialogs the user row is a
space-between flex row; a long displayName grew unbounded and pushed the checkbox
off the right edge. Cap userName at width: '70%' so the checkbox always has room.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feat: Upgrade to React Native 0.86 (New Architecture / TurboModule) + SPM opt-in for ZelloSDK
…ncoming emergency

Ports the following native SDK features to the React Native SDK:
- Channel type (ZelloChannel.channelType / ZelloChannelType) - ANDROID-3816 / IOS-4385
- Translation flags (ZelloChannel.translationsEnabled, ZelloIncomingVoiceMessage.isTranslation) - ANDROID-3706
- Voice message transcriptions (ZelloHistoryVoiceMessage.transcription, ZelloTranscription,
  HISTORY_VOICE_MESSAGE_TRANSCRIPTION_AVAILABLE event) - ANDROID-3685 / IOS-4252
- Stop incoming emergency (Zello.stopIncomingEmergency, allowEmergencyEndOwn/Others
  channel options) - ANDROID-3683 / IOS-4251

Bumps native dependencies: com.zello:sdk to 2.+ and ZelloSDK pod to ~> 3.0.
Wires the example app: transcription text in the history dialog and a
"Stop Incoming Emergency" context menu option.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

# Conflicts:
#	CHANGELOG.md
#	android/build.gradle
#	android/src/main/java/com/zellosdk/ZelloAndroidSdkModule.kt
#	react-native-zello-sdk.podspec
…-native-zello-sdk into feature/native-sdk-parity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants