diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 31bb301..88a59d2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,6 +26,9 @@ jobs: node-version: '22.x' registry-url: 'https://registry.npmjs.org' + - name: Upgrade npm + run: npm install -g npm@11.7.0 + - name: Build run: | yarn diff --git a/android/build.gradle b/android/build.gradle index e9d5533..b724e22 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -80,7 +80,7 @@ def kotlin_version = getExtOrDefault("kotlinVersion") dependencies { implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "io.qonversion:sandwich:7.3.1" + implementation "io.qonversion:sandwich:7.4.0" } if (isNewArchitectureEnabled()) { diff --git a/android/src/main/java/com/qonversion/reactnativesdk/NoCodesModule.kt b/android/src/main/java/com/qonversion/reactnativesdk/NoCodesModule.kt index 39eb61a..cc6ddb6 100644 --- a/android/src/main/java/com/qonversion/reactnativesdk/NoCodesModule.kt +++ b/android/src/main/java/com/qonversion/reactnativesdk/NoCodesModule.kt @@ -43,9 +43,9 @@ class NoCodesModule(private val reactContext: ReactApplicationContext) : NativeN } @ReactMethod - override fun initialize(projectKey: String, source: String, version: String, proxyUrl: String?, locale: String?) { + override fun initialize(projectKey: String, source: String, version: String, proxyUrl: String?, locale: String?, theme: String?) { noCodesSandwich.storeSdkInfo(reactContext, source, version) - noCodesSandwich.initialize(reactContext, projectKey, null, null, proxyUrl, locale) + noCodesSandwich.initialize(reactContext, projectKey, null, null, proxyUrl, locale, theme) noCodesSandwich.setDelegate(noCodesEventListener) noCodesSandwich.setScreenCustomizationDelegate() } @@ -111,6 +111,11 @@ class NoCodesModule(private val reactContext: ReactApplicationContext) : NativeN noCodesSandwich.setLocale(locale) } + @ReactMethod + override fun setTheme(theme: String?) { + noCodesSandwich.setTheme(theme) + } + companion object { const val NAME = "RNNoCodes" } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3155d6d..fa5f197 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -8,16 +8,16 @@ PODS: - hermes-engine (0.80.1): - hermes-engine/Pre-built (= 0.80.1) - hermes-engine/Pre-built (0.80.1) - - Qonversion (6.3.0): - - Qonversion/Main (= 6.3.0) - - qonversion-react-native-sdk (10.0.2): + - Qonversion (6.4.0): + - Qonversion/Main (= 6.4.0) + - qonversion-react-native-sdk (10.1.1): - boost - DoubleConversion - fast_float - fmt - glog - hermes-engine - - QonversionSandwich (= 7.3.0) + - QonversionSandwich (= 7.4.0) - RCT-Folly - RCT-Folly/Fabric - RCTRequired @@ -40,9 +40,9 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - Qonversion/Main (6.3.0) - - QonversionSandwich (7.3.0): - - Qonversion (= 6.3.0) + - Qonversion/Main (6.4.0) + - QonversionSandwich (7.4.0): + - Qonversion (= 6.4.0) - RCT-Folly (2024.11.18.00): - boost - DoubleConversion @@ -2437,9 +2437,9 @@ SPEC CHECKSUMS: fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 4f07404533b808de66cf48ac4200463068d0e95a - Qonversion: 8f8fb604e2b9fb442eeb6a7aefcbc6bd73c3b55a - qonversion-react-native-sdk: a11f2ab3bb7f7a89d8c16523e8b7b90985358455 - QonversionSandwich: 046f2896c41037f3f58df63e4f22bd351312eeef + Qonversion: f7620e1cc3b03541b9cf4a0ed357a5646d123fe3 + qonversion-react-native-sdk: a0520e74e9840f14b110f806dd1e7cbd3d093fae + QonversionSandwich: 58a58f43664d2ddb755b315b51adfe9392ede3f9 RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f RCTDeprecation: efa5010912100e944a7ac9a93a157e1def1988fe RCTRequired: bbc4cf999ddc4a4b076e076c74dd1d39d0254630 diff --git a/example/src/screens/NoCodesScreen/index.tsx b/example/src/screens/NoCodesScreen/index.tsx index 4c1edab..0d4c291 100644 --- a/example/src/screens/NoCodesScreen/index.tsx +++ b/example/src/screens/NoCodesScreen/index.tsx @@ -15,6 +15,7 @@ import Qonversion, { ScreenPresentationConfig, NoCodes, NoCodesError, + NoCodesTheme, type PurchaseDelegate, Product } from '@qonversion/react-native-sdk'; @@ -35,6 +36,7 @@ const NoCodesScreen: React.FC = () => { ); const [animated, setAnimated] = useState(false); const [locale, setLocale] = useState(''); + const [theme, setTheme] = useState(NoCodesTheme.AUTO); useEffect(() => { // Initialize No-Codes SDK once @@ -216,6 +218,22 @@ const NoCodesScreen: React.FC = () => { } }; + const applyTheme = (selectedTheme: NoCodesTheme) => { + try { + console.log('🔄 [NoCodes] Setting theme to:', selectedTheme); + setTheme(selectedTheme); + NoCodes.getSharedInstance().setTheme(selectedTheme); + console.log('✅ [NoCodes] setTheme() call successful'); + Snackbar.show({ + text: `Theme set to: ${selectedTheme}`, + duration: Snackbar.LENGTH_SHORT, + }); + } catch (error: any) { + console.error('❌ [NoCodes] setTheme() call failed:', error); + Alert.alert('Error', error.message); + } + }; + return ( { + + Theme + Select theme mode: + {Object.values(NoCodesTheme).map((themeOption) => ( + applyTheme(themeOption)} + > + {themeOption} + + ))} + + Close diff --git a/ios/RNNoCodes.mm b/ios/RNNoCodes.mm index afadd01..be15757 100644 --- a/ios/RNNoCodes.mm +++ b/ios/RNNoCodes.mm @@ -26,8 +26,9 @@ - (void)initialize:(NSString *)projectKey source:(NSString *)source version:(NSString *)version proxyUrl:(NSString *)proxyUrl - locale:(NSString *)locale { - [self.impl initializeWithProjectKey:projectKey source:source version:version proxyUrl:proxyUrl locale:locale]; + locale:(NSString *)locale + theme:(NSString *)theme { + [self.impl initializeWithProjectKey:projectKey source:source version:version proxyUrl:proxyUrl locale:locale theme:theme]; } - (void)setScreenPresentationConfig:(NSDictionary *)configData @@ -76,6 +77,10 @@ - (void)setLocale:(NSString *)locale { [self.impl setLocale:locale]; } +- (void)setTheme:(NSString *)theme { + [self.impl setTheme:theme]; +} + #pragma mark - NoCodesEventDelegate - (void)noCodesDidTriggerWithEvent:(NSString * _Nonnull)event payload:(NSDictionary * _Nullable)payload { diff --git a/ios/RNNoCodesImpl.swift b/ios/RNNoCodesImpl.swift index d6a04fe..02db9af 100644 --- a/ios/RNNoCodesImpl.swift +++ b/ios/RNNoCodesImpl.swift @@ -65,9 +65,9 @@ public class RNNoCodesImpl: NSObject { } @objc - public func initialize(projectKey: String, source: String, version: String, proxyUrl: String?, locale: String?) { + public func initialize(projectKey: String, source: String, version: String, proxyUrl: String?, locale: String?, theme: String?) { // Ignore source and version, because it's taken from the Qonversion SDK. - noCodesSandwich?.initialize(projectKey: projectKey, proxyUrl: proxyUrl, locale: locale) + noCodesSandwich?.initialize(projectKey: projectKey, proxyUrl: proxyUrl, locale: locale, theme: theme) } @MainActor @objc @@ -121,4 +121,9 @@ public class RNNoCodesImpl: NSObject { public func setLocale(_ locale: String?) { noCodesSandwich?.setLocale(locale) } + + @objc + public func setTheme(_ theme: String?) { + noCodesSandwich?.setTheme(theme) + } } diff --git a/package.json b/package.json index ad1aecf..232503d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@qonversion/react-native-sdk", "title": "React Native Qonversion", - "version": "10.1.1", + "version": "10.2.0", "description": "Qonversion provides full in-app purchases infrastructure, so you do not need to build your own server for receipt validation. Implement in-app subscriptions, validate user receipts, check subscription status, and provide access to your app features and content using our StoreKit wrapper and Google Play Billing wrapper.", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/qonversion-react-native-sdk.podspec b/qonversion-react-native-sdk.podspec index 8991e65..bf2a464 100644 --- a/qonversion-react-native-sdk.podspec +++ b/qonversion-react-native-sdk.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" s.private_header_files = "ios/**/*.h" - s.dependency "QonversionSandwich", "7.3.1" + s.dependency "QonversionSandwich", "7.4.0" install_modules_dependencies(s) end diff --git a/src/NoCodesApi.ts b/src/NoCodesApi.ts index de4e583..83f4ee4 100644 --- a/src/NoCodesApi.ts +++ b/src/NoCodesApi.ts @@ -1,4 +1,5 @@ import ScreenPresentationConfig from './dto/ScreenPresentationConfig'; +import type { NoCodesTheme } from './dto/enums'; export default interface NoCodesApi { /** @@ -28,4 +29,13 @@ export default interface NoCodesApi { * @param locale the locale to use (e.g. "en", "de", "fr"), or null to reset to device default. */ setLocale(locale: string | null): void; + + /** + * Set the theme mode for No-Code screens. + * Controls how screens adapt to light/dark themes. + * + * @param theme the desired theme mode. Use {@link NoCodesTheme.AUTO} to follow device settings, + * {@link NoCodesTheme.LIGHT} to force light theme, or {@link NoCodesTheme.DARK} to force dark theme. + */ + setTheme(theme: NoCodesTheme): void; } diff --git a/src/NoCodesConfig.ts b/src/NoCodesConfig.ts index c372fc3..c16c06f 100644 --- a/src/NoCodesConfig.ts +++ b/src/NoCodesConfig.ts @@ -1,5 +1,6 @@ import type { NoCodesListener } from './dto/NoCodesListener'; import type { PurchaseDelegate } from './dto/PurchaseDelegate'; +import type { NoCodesTheme } from './dto/enums'; class NoCodesConfig { readonly projectKey: string; @@ -7,19 +8,22 @@ class NoCodesConfig { readonly purchaseDelegate: PurchaseDelegate | undefined; readonly proxyUrl: string | undefined; readonly locale: string | undefined; + readonly theme: NoCodesTheme | undefined; constructor( projectKey: string, noCodesListener: NoCodesListener | undefined = undefined, purchaseDelegate: PurchaseDelegate | undefined = undefined, proxyUrl: string | undefined = undefined, - locale: string | undefined = undefined + locale: string | undefined = undefined, + theme: NoCodesTheme | undefined = undefined ) { this.projectKey = projectKey; this.noCodesListener = noCodesListener; this.purchaseDelegate = purchaseDelegate; this.proxyUrl = proxyUrl; this.locale = locale; + this.theme = theme; } } diff --git a/src/NoCodesConfigBuilder.ts b/src/NoCodesConfigBuilder.ts index c495d84..b72939a 100644 --- a/src/NoCodesConfigBuilder.ts +++ b/src/NoCodesConfigBuilder.ts @@ -1,5 +1,6 @@ import type {NoCodesListener} from './dto/NoCodesListener'; import type {PurchaseDelegate} from './dto/PurchaseDelegate'; +import {NoCodesTheme} from './dto/enums'; import NoCodesConfig from './NoCodesConfig'; class NoCodesConfigBuilder { @@ -8,6 +9,7 @@ class NoCodesConfigBuilder { private purchaseDelegate: PurchaseDelegate | undefined = undefined; private proxyUrl: string | undefined = undefined; private locale: string | undefined = undefined; + private theme: NoCodesTheme | undefined = undefined; constructor(projectKey: string) { this.projectKey = projectKey; @@ -63,6 +65,19 @@ class NoCodesConfigBuilder { return this; } + /** + * Set the theme mode for No-Code screens. + * Controls how screens adapt to light/dark themes. + * + * @param theme the desired theme mode. Use {@link NoCodesTheme.AUTO} to follow device settings, + * {@link NoCodesTheme.LIGHT} to force light theme, or {@link NoCodesTheme.DARK} to force dark theme. + * @return builder instance for chain calls. + */ + setTheme(theme: NoCodesTheme): NoCodesConfigBuilder { + this.theme = theme; + return this; + } + /** * Generate {@link NoCodesConfig} instance with all the provided configurations. * @@ -74,7 +89,8 @@ class NoCodesConfigBuilder { this.noCodesListener, this.purchaseDelegate, this.proxyUrl, - this.locale + this.locale, + this.theme ); } } diff --git a/src/dto/enums.ts b/src/dto/enums.ts index 3d8ab15..7a80a2e 100644 --- a/src/dto/enums.ts +++ b/src/dto/enums.ts @@ -366,6 +366,28 @@ export enum QonversionErrorCode { UNKNOWN_CLIENT_PLATFORM = "UnknownClientPlatform", // The current platform is not supported } +/** + * Theme mode for No-Code screens. + * Use this to control how screens adapt to light/dark themes. + */ +export enum NoCodesTheme { + /** + * Automatically follow the device's system appearance (default). + * The screen will use light theme in light mode and dark theme in dark mode. + */ + AUTO = 'auto', + + /** + * Force light theme regardless of device settings. + */ + LIGHT = 'light', + + /** + * Force dark theme regardless of device settings. + */ + DARK = 'dark', +} + export enum NoCodesErrorCode { UNKNOWN = "Unknown", BAD_NETWORK_REQUEST = "BadNetworkRequest", diff --git a/src/internal/NoCodesInternal.ts b/src/internal/NoCodesInternal.ts index fedb06b..6d1b539 100644 --- a/src/internal/NoCodesInternal.ts +++ b/src/internal/NoCodesInternal.ts @@ -5,7 +5,7 @@ import type {NoCodesListener} from '../dto/NoCodesListener'; import type {PurchaseDelegate} from '../dto/PurchaseDelegate'; import ScreenPresentationConfig from '../dto/ScreenPresentationConfig'; import NoCodesError from '../dto/NoCodesError'; -import {NoCodesErrorCode} from '../dto/enums'; +import {NoCodesErrorCode, NoCodesTheme} from '../dto/enums'; import RNNoCodes, {type NoCodeEvent} from './specs/NativeNoCodesModule'; import {sdkSource, sdkVersion} from './QonversionInternal'; import Product from '../dto/Product'; @@ -22,7 +22,7 @@ export default class NoCodesInternal implements NoCodesApi { private purchaseDelegate: PurchaseDelegate | null = null; constructor(config: NoCodesConfig) { - RNNoCodes.initialize(config.projectKey, sdkSource, sdkVersion, config.proxyUrl, config.locale); + RNNoCodes.initialize(config.projectKey, sdkSource, sdkVersion, config.proxyUrl, config.locale, config.theme); if (config.noCodesListener) { this.setNoCodesListener(config.noCodesListener); @@ -122,4 +122,8 @@ export default class NoCodesInternal implements NoCodesApi { setLocale(locale: string | null) { RNNoCodes.setLocale(locale); } + + setTheme(theme: NoCodesTheme) { + RNNoCodes.setTheme(theme); + } } diff --git a/src/internal/QonversionInternal.ts b/src/internal/QonversionInternal.ts index 3468aed..2e486ed 100644 --- a/src/internal/QonversionInternal.ts +++ b/src/internal/QonversionInternal.ts @@ -24,7 +24,7 @@ import PromotionalOffer from '../dto/PromotionalOffer'; import RNQonversion from './specs/NativeQonversionModule'; import type { QPromoOfferDetails } from './specs/NativeQonversionModule'; -export const sdkVersion = "10.1.1"; +export const sdkVersion = "10.2.0"; export const sdkSource = "rn"; export default class QonversionInternal implements QonversionApi { diff --git a/src/internal/specs/NativeNoCodesModule.ts b/src/internal/specs/NativeNoCodesModule.ts index 79f389e..14c0f5a 100644 --- a/src/internal/specs/NativeNoCodesModule.ts +++ b/src/internal/specs/NativeNoCodesModule.ts @@ -9,12 +9,13 @@ export type NoCodeEvent = { }; export interface Spec extends TurboModule { - initialize(projectKey: string, source: string, version: string, proxyUrl?: string, locale?: string): void; + initialize(projectKey: string, source: string, version: string, proxyUrl?: string, locale?: string, theme?: string): void; setScreenPresentationConfig(configData: Object, contextKey?: string): Promise; showScreen(contextKey: string): Promise; close(): Promise; setPurchaseDelegate(): void; setLocale(locale: string | null): void; + setTheme(theme: string): void; // Methods to notify native code about purchase/restore results delegatedPurchaseCompleted(): void;