From af9bc628df993b59f7a74fe91b4138b11cd7406a Mon Sep 17 00:00:00 2001 From: Surik Date: Thu, 30 Apr 2026 00:11:25 +0300 Subject: [PATCH 1/2] feat(nocodes): add custom variables and forceSendProperties bridges Bumps QonversionSandwich to 7.9.0 to consume new Sandwich APIs. --- NOCODES_UNITY_IMPLEMENTATION_PROMPT.md | 375 ++++++++++++++++++ android/build.gradle | 2 +- .../qonversion_flutter_sdk/NoCodesPlugin.kt | 4 +- .../QonversionPlugin.kt | 11 +- ios/Classes/NoCodesPlugin.swift | 6 +- ios/Classes/SwiftQonversionPlugin.swift | 9 + ios/qonversion_flutter.podspec | 2 +- lib/src/internal/constants.dart | 2 + lib/src/internal/nocodes_internal.dart | 10 +- lib/src/internal/qonversion_internal.dart | 5 + lib/src/nocodes.dart | 10 +- lib/src/qonversion.dart | 5 + macos/qonversion_flutter.podspec | 2 +- 13 files changed, 429 insertions(+), 14 deletions(-) create mode 100644 NOCODES_UNITY_IMPLEMENTATION_PROMPT.md diff --git a/NOCODES_UNITY_IMPLEMENTATION_PROMPT.md b/NOCODES_UNITY_IMPLEMENTATION_PROMPT.md new file mode 100644 index 00000000..7ed630d5 --- /dev/null +++ b/NOCODES_UNITY_IMPLEMENTATION_PROMPT.md @@ -0,0 +1,375 @@ +# Промпт для реализации NoCodes в Unity SDK + +## Контекст +В существующем Unity SDK для Qonversion есть только основная функциональность Qonversion (инициализация, покупки, entitlements и т.д.), но отсутствует модуль NoCodes. Необходимо реализовать NoCodes функциональность, аналогичную той, что реализована во Flutter SDK. + +## Архитектура и требования + +### 1. Основной API класс `NoCodes` + +Создать статический класс `NoCodes` с паттерном Singleton, аналогичный Flutter реализации: + +**Основные методы:** +- `Initialize(NoCodesConfig config)` - инициализация NoCodes SDK с конфигурацией +- `InitializeWithProjectKey(string projectKey)` - упрощенная инициализация только с project key (для обратной совместимости) +- `GetSharedInstance()` - получение текущего инициализированного экземпляра (throws exception если не инициализирован) + +**События (Events/Callbacks):** +Реализовать систему событий для 6 типов событий NoCodes: +- `ScreenShown` - событие когда NoCodes экран показан +- `Finished` - событие когда NoCodes flow завершен +- `ActionStarted` - событие когда действие начато +- `ActionFailed` - событие когда действие провалилось +- `ActionFinished` - событие когда действие завершено +- `ScreenFailedToLoad` - событие когда экран не смог загрузиться + +**Методы управления экраном:** +- `SetScreenPresentationConfig(NoCodesPresentationConfig config, string contextKey = null)` - установка конфигурации презентации экрана +- `ShowScreen(string contextKey)` - показать NoCodes экран с указанным context key +- `Close()` - закрыть текущий NoCodes экран + +### 2. Классы конфигурации + +**NoCodesConfig:** +```csharp +public class NoCodesConfig +{ + public string ProjectKey { get; } + public string ProxyUrl { get; } + + public NoCodesConfig(string projectKey, string proxyUrl = null) + { + ProjectKey = projectKey; + ProxyUrl = proxyUrl; + } +} +``` + +**NoCodesConfigBuilder:** +```csharp +public class NoCodesConfigBuilder +{ + private readonly string _projectKey; + private string _proxyUrl; + + public NoCodesConfigBuilder(string projectKey) + { + _projectKey = projectKey; + } + + public NoCodesConfigBuilder SetProxyUrl(string proxyUrl) + { + _proxyUrl = proxyUrl; + return this; + } + + public NoCodesConfig Build() + { + return new NoCodesConfig(_projectKey, _proxyUrl); + } +} +``` + +**NoCodesPresentationConfig:** +```csharp +public enum NoCodesPresentationStyle +{ + Push, + FullScreen, + Popover +} + +public class NoCodesPresentationConfig +{ + public bool Animated { get; } + public NoCodesPresentationStyle PresentationStyle { get; } + + public NoCodesPresentationConfig(bool animated = true, + NoCodesPresentationStyle presentationStyle = NoCodesPresentationStyle.FullScreen) + { + Animated = animated; + PresentationStyle = presentationStyle; + } + + // Метод для конвертации в Dictionary для передачи в нативный код + public Dictionary ToDictionary() + { + string styleString = PresentationStyle switch + { + NoCodesPresentationStyle.Push => "Push", + NoCodesPresentationStyle.FullScreen => "FullScreen", + NoCodesPresentationStyle.Popover => "Popover", + _ => "FullScreen" + }; + + return new Dictionary + { + { "animated", Animated }, + { "presentationStyle", styleString } + }; + } +} +``` + +### 3. Классы событий + +**Базовый класс события:** +```csharp +public abstract class NoCodesEvent +{ + public Dictionary Payload { get; } + + protected NoCodesEvent(Dictionary payload = null) + { + Payload = payload ?? new Dictionary(); + } +} +``` + +**Конкретные классы событий:** +```csharp +public class NoCodesScreenShownEvent : NoCodesEvent +{ + public NoCodesScreenShownEvent(Dictionary payload = null) + : base(payload) { } +} + +public class NoCodesFinishedEvent : NoCodesEvent +{ + public NoCodesFinishedEvent(Dictionary payload = null) + : base(payload) { } +} + +public class NoCodesActionStartedEvent : NoCodesEvent +{ + public NoCodesActionStartedEvent(Dictionary payload = null) + : base(payload) { } +} + +public class NoCodesActionFailedEvent : NoCodesEvent +{ + public NoCodesActionFailedEvent(Dictionary payload = null) + : base(payload) { } +} + +public class NoCodesActionFinishedEvent : NoCodesEvent +{ + public NoCodesActionFinishedEvent(Dictionary payload = null) + : base(payload) { } +} + +public class NoCodesScreenFailedToLoadEvent : NoCodesEvent +{ + public NoCodesScreenFailedToLoadEvent(Dictionary payload = null) + : base(payload) { } +} +``` + +### 4. Нативная интеграция + +**Android (Java/Kotlin):** +- Создать класс `NoCodesUnityPlugin` аналогичный `NoCodesPlugin` из Flutter SDK +- Использовать `NoCodesSandwich` из библиотеки `io.qonversion:sandwich` +- Реализовать `NoCodesEventListener` для обработки событий +- Методы для вызова из Unity: + - `initializeNoCodes(String projectKey, String version, String source, String proxyUrl)` + - `setScreenPresentationConfig(Map config, String contextKey)` + - `showNoCodesScreen(String contextKey)` + - `closeNoCodes()` +- Отправка событий в Unity через UnitySendMessage для каждого типа события + +**iOS (Objective-C/Swift):** +- Создать класс `NoCodesUnityPlugin` аналогичный `NoCodesPlugin` из Flutter SDK +- Использовать `NoCodesSandwich` из QonversionSandwich framework +- Реализовать `NoCodesEventListener` протокол +- Методы для вызова из Unity: + - `initializeNoCodes(projectKey:version:source:proxyUrl:)` + - `setScreenPresentationConfig(_:forContextKey:)` + - `showScreen(_:)` + - `close()` +- Отправка событий в Unity через UnitySendMessage для каждого типа события + +### 5. Константы и имена методов + +**Методы для MethodChannel/Unity:** +- `initializeNoCodes` - инициализация +- `setScreenPresentationConfig` - установка конфигурации презентации +- `showNoCodesScreen` - показать экран +- `closeNoCodes` - закрыть экран + +**Имена событий для передачи в Unity:** +- `nocodes_screen_shown` +- `nocodes_finished` +- `nocodes_action_started` +- `nocodes_action_failed` +- `nocodes_action_finished` +- `nocodes_screen_failed_to_load` + +**Параметры:** +- `projectKey` - ключ проекта Qonversion +- `version` - версия SDK (например, "10.0.3") +- `source` - источник SDK (например, "unity") +- `proxyUrl` - опциональный URL прокси +- `config` - конфигурация презентации (Dictionary с `animated` и `presentationStyle`) +- `contextKey` - ключ контекста для показа экрана +- `payload` - данные события (Dictionary) + +### 6. Особенности реализации + +**Платформенная поддержка:** +- ✅ iOS: Полная поддержка +- ✅ Android: Полная поддержка +- ❌ Другие платформы: No-op методы (ничего не делают, не выбрасывают ошибки) + +**Обработка ошибок:** +- Все методы должны быть безопасными и не выбрасывать исключения в Unity +- При ошибках инициализации логировать ошибку, но не крашить приложение +- События должны обрабатываться асинхронно + +**Потокобезопасность:** +- Singleton должен быть потокобезопасным +- События должны обрабатываться на главном потоке Unity + +### 7. Пример использования + +```csharp +using Qonversion.Unity; + +public class NoCodesExample : MonoBehaviour +{ + void Start() + { + // Инициализация через builder + var config = new NoCodesConfigBuilder("your_project_key") + .SetProxyUrl("https://proxy.example.com") + .Build(); + + NoCodes.Initialize(config); + + // Подписка на события + NoCodes.ScreenShown += OnScreenShown; + NoCodes.Finished += OnFinished; + NoCodes.ActionStarted += OnActionStarted; + NoCodes.ActionFailed += OnActionFailed; + NoCodes.ActionFinished += OnActionFinished; + NoCodes.ScreenFailedToLoad += OnScreenFailedToLoad; + } + + void OnScreenShown(NoCodesScreenShownEvent evt) + { + Debug.Log($"Screen shown with payload: {JsonUtility.ToJson(evt.Payload)}"); + } + + void OnFinished(NoCodesFinishedEvent evt) + { + Debug.Log($"Finished with payload: {JsonUtility.ToJson(evt.Payload)}"); + } + + // ... другие обработчики событий + + public async void ShowNoCodesScreen() + { + // Установка конфигурации презентации + var presentationConfig = new NoCodesPresentationConfig( + animated: true, + presentationStyle: NoCodesPresentationStyle.FullScreen + ); + + await NoCodes.GetSharedInstance().SetScreenPresentationConfig( + presentationConfig, + contextKey: "my_context_key" + ); + + // Показ экрана + await NoCodes.GetSharedInstance().ShowScreen("my_context_key"); + } + + public async void CloseNoCodesScreen() + { + await NoCodes.GetSharedInstance().Close(); + } + + void OnDestroy() + { + // Отписка от событий + NoCodes.ScreenShown -= OnScreenShown; + NoCodes.Finished -= OnFinished; + // ... остальные отписки + } +} +``` + +### 8. Структура файлов + +``` +QonversionUnity/ +├── Runtime/ +│ ├── NoCodes/ +│ │ ├── NoCodes.cs (основной API класс) +│ │ ├── NoCodesConfig.cs +│ │ ├── NoCodesConfigBuilder.cs +│ │ ├── NoCodesPresentationConfig.cs +│ │ ├── Events/ +│ │ │ ├── NoCodesEvent.cs (базовый класс) +│ │ │ ├── NoCodesScreenShownEvent.cs +│ │ │ ├── NoCodesFinishedEvent.cs +│ │ │ ├── NoCodesActionStartedEvent.cs +│ │ │ ├── NoCodesActionFailedEvent.cs +│ │ │ ├── NoCodesActionFinishedEvent.cs +│ │ │ └── NoCodesScreenFailedToLoadEvent.cs +│ │ └── Internal/ +│ │ └── NoCodesInternal.cs (внутренняя реализация) +├── Plugins/ +│ ├── Android/ +│ │ └── NoCodesUnityPlugin.java (или .kt) +│ └── iOS/ +│ └── NoCodesUnityPlugin.m (или .swift) +``` + +### 9. Зависимости + +**Android:** +- Использовать существующую зависимость `io.qonversion:sandwich` (уже должна быть в проекте для основного Qonversion SDK) +- `NoCodesSandwich` класс из этой библиотеки + +**iOS:** +- Использовать существующий QonversionSandwich framework (уже должен быть в проекте) +- `NoCodesSandwich` класс из этого framework + +### 10. Тестирование + +Реализовать: +- Unit тесты для C# классов +- Интеграционные тесты для нативной интеграции +- Пример сцены в Unity с UI для демонстрации функциональности + +### 11. Документация + +Добавить: +- XML комментарии для всех публичных методов и классов +- README с примерами использования +- Миграционный гайд для пользователей, переходящих с других SDK + +## Важные замечания + +1. **Совместимость**: Реализация должна быть совместима с существующим Qonversion SDK в Unity и не ломать существующую функциональность + +2. **Асинхронность**: Все методы, которые вызывают нативный код, должны быть асинхронными (async/await) + +3. **События**: Использовать C# события (events) для подписки на события NoCodes, аналогично тому как это сделано в основном Qonversion SDK + +4. **JSON сериализация**: Использовать Unity JsonUtility или Newtonsoft.Json для сериализации/десериализации payload событий + +5. **Версионирование**: Версия SDK должна передаваться в нативный код при инициализации (например, "10.0.3") + +6. **Source идентификация**: При инициализации передавать `source: "unity"` для идентификации SDK в аналитике Qonversion + +## Референсная реализация + +Для понимания деталей реализации можно использовать: +- Flutter SDK: `/lib/src/nocodes.dart` и связанные файлы +- Android нативная часть: `/android/src/main/kotlin/.../NoCodesPlugin.kt` +- iOS нативная часть: `/ios/Classes/NoCodesPlugin.swift` + +Все эти файлы содержат полную реализацию, которую можно адаптировать для Unity. + diff --git a/android/build.gradle b/android/build.gradle index dbd8d940..491d9453 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -51,6 +51,6 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "io.qonversion:sandwich:7.8.0" + implementation "io.qonversion:sandwich:7.9.0" implementation 'com.google.code.gson:gson:2.9.0' } diff --git a/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/NoCodesPlugin.kt b/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/NoCodesPlugin.kt index fa5f28a9..fb15a29e 100644 --- a/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/NoCodesPlugin.kt +++ b/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/NoCodesPlugin.kt @@ -106,9 +106,9 @@ class NoCodesPlugin(private val messenger: BinaryMessenger, private val context: } } - fun showNoCodesScreen(contextKey: String?, result: Result) { + fun showNoCodesScreen(contextKey: String?, customVariables: Map?, result: Result) { if (contextKey != null) { - noCodesSandwich?.showScreen(contextKey) + noCodesSandwich?.showScreen(contextKey, customVariables) result.success(null) } else { result.noNecessaryDataError() diff --git a/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionPlugin.kt b/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionPlugin.kt index d1a463fa..28c46741 100644 --- a/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionPlugin.kt +++ b/android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionPlugin.kt @@ -105,6 +105,9 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware { "userProperties" -> { return userProperties(result) } + "forceSendProperties" -> { + return forceSendProperties(result) + } "logout" -> { qonversionSandwich.logout() return result.success(null) @@ -159,7 +162,7 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware { // NoCodes methods "initializeNoCodes" -> noCodesPlugin?.initializeNoCodes(args, result) "setScreenPresentationConfig" -> noCodesPlugin?.setScreenPresentationConfig(args["config"] as? Map, args["contextKey"] as? String, result) - "showNoCodesScreen" -> noCodesPlugin?.showNoCodesScreen(args["contextKey"] as? String, result) + "showNoCodesScreen" -> noCodesPlugin?.showNoCodesScreen(args["contextKey"] as? String, args["customVariables"] as? Map, result) "setNoCodesLocale" -> noCodesPlugin?.setLocale(args["locale"] as? String, result) "setNoCodesTheme" -> noCodesPlugin?.setTheme(args["theme"] as? String, result) // NoCodes Purchase Delegate methods @@ -252,6 +255,12 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware { qonversionSandwich.userProperties(result.toJsonResultListener()) } + private fun forceSendProperties(result: Result) { + qonversionSandwich.forceSendProperties { + result.success(null) + } + } + private fun isFallbackFileAccessible(result: Result) { qonversionSandwich.isFallbackFileAccessible(result.toJsonResultListener()) } diff --git a/ios/Classes/NoCodesPlugin.swift b/ios/Classes/NoCodesPlugin.swift index 8847fcca..30b8062b 100644 --- a/ios/Classes/NoCodesPlugin.swift +++ b/ios/Classes/NoCodesPlugin.swift @@ -112,8 +112,10 @@ public class NoCodesPlugin: NSObject { let contextKey = args["contextKey"] as? String else { return result(FlutterError.noNecessaryData) } - - noCodesSandwich?.showScreen(contextKey) + + let customVariables = args["customVariables"] as? [String: String] + + noCodesSandwich?.showScreen(contextKey, customVariables: customVariables) result(nil) } diff --git a/ios/Classes/SwiftQonversionPlugin.swift b/ios/Classes/SwiftQonversionPlugin.swift index f3a31657..57a8742e 100644 --- a/ios/Classes/SwiftQonversionPlugin.swift +++ b/ios/Classes/SwiftQonversionPlugin.swift @@ -84,6 +84,9 @@ public class SwiftQonversionPlugin: NSObject, FlutterPlugin { case "userProperties": return userProperties(result) + case "forceSendProperties": + return forceSendProperties(result) + case "presentCodeRedemptionSheet": return presentCodeRedemptionSheet(result) @@ -312,6 +315,12 @@ public class SwiftQonversionPlugin: NSObject, FlutterPlugin { private func userProperties(_ result: @escaping FlutterResult) { qonversionSandwich?.userProperties(getJsonCompletion(result)) } + + private func forceSendProperties(_ result: @escaping FlutterResult) { + qonversionSandwich?.forceSendProperties { + result(nil) + } + } private func restore(_ result: @escaping FlutterResult) { qonversionSandwich?.restore(getJsonCompletion(result)) diff --git a/ios/qonversion_flutter.podspec b/ios/qonversion_flutter.podspec index e29bcac7..ec450459 100644 --- a/ios/qonversion_flutter.podspec +++ b/ios/qonversion_flutter.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '13.0' - s.dependency "QonversionSandwich", "7.8.0" + s.dependency "QonversionSandwich", "7.9.0" # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } diff --git a/lib/src/internal/constants.dart b/lib/src/internal/constants.dart index 861b4be3..23cb7283 100644 --- a/lib/src/internal/constants.dart +++ b/lib/src/internal/constants.dart @@ -38,6 +38,7 @@ class Constants { static const kSource = 'source'; static const kLocale = 'locale'; static const kTheme = 'theme'; + static const kCustomVariables = 'customVariables'; // MethodChannel methods names static const mInitialize = 'initialize'; @@ -54,6 +55,7 @@ class Constants { static const mSetDefinedUserProperty = 'setDefinedUserProperty'; static const mSetCustomUserProperty = 'setCustomUserProperty'; static const mUserProperties = 'userProperties'; + static const mForceSendProperties = 'forceSendProperties'; static const mSetEntitlementsCacheLifetime = 'setEntitlementsCacheLifetime'; static const mSyncPurchases = 'syncPurchases'; static const mAddAttributionData = 'addAttributionData'; diff --git a/lib/src/internal/nocodes_internal.dart b/lib/src/internal/nocodes_internal.dart index 5dff415d..95656b2a 100644 --- a/lib/src/internal/nocodes_internal.dart +++ b/lib/src/internal/nocodes_internal.dart @@ -167,12 +167,16 @@ class NoCodesInternal implements NoCodes { } @override - Future showScreen(String contextKey) async { + Future showScreen(String contextKey, {Map? customVariables}) async { if (Platform.isMacOS) { return; } - - await _invokeMethod(Constants.mShowNoCodesScreen, {Constants.kContextKey: contextKey}); + + final args = { + Constants.kContextKey: contextKey, + if (customVariables != null) Constants.kCustomVariables: customVariables, + }; + await _invokeMethod(Constants.mShowNoCodesScreen, args); } @override diff --git a/lib/src/internal/qonversion_internal.dart b/lib/src/internal/qonversion_internal.dart index 9895c61a..92294a6a 100644 --- a/lib/src/internal/qonversion_internal.dart +++ b/lib/src/internal/qonversion_internal.dart @@ -420,6 +420,11 @@ class QonversionInternal implements Qonversion { }); } + @override + Future forceSendProperties() async { + await _invokeMethod(Constants.mForceSendProperties); + } + @override Future userProperties() async { final rawResult = await _invokeMethod(Constants.mUserProperties); diff --git a/lib/src/nocodes.dart b/lib/src/nocodes.dart index be001af2..5504d962 100644 --- a/lib/src/nocodes.dart +++ b/lib/src/nocodes.dart @@ -92,10 +92,14 @@ abstract class NoCodes { String? contextKey, }); - /// Show No-Codes screen with context key - /// + /// Show No-Codes screen with context key. + /// + /// Optionally pass [customVariables] — a map of custom variables that will be + /// injected into the screen's JavaScript context. Variables are scoped to the + /// provided [contextKey] and only applied to that screen. + /// /// **Platform Support:** iOS and Android. No-op on macOS. - Future showScreen(String contextKey); + Future showScreen(String contextKey, {Map? customVariables}); /// Close No-Codes screen /// diff --git a/lib/src/qonversion.dart b/lib/src/qonversion.dart index 0c8febf8..6237e0fd 100644 --- a/lib/src/qonversion.dart +++ b/lib/src/qonversion.dart @@ -191,6 +191,11 @@ abstract class Qonversion { /// in the result. Future userProperties(); + /// Force-flushes any pending user property updates to the server immediately. + /// Use this when you need to ensure all previously set properties have been sent + /// before performing an operation that depends on them. + Future forceSendProperties(); + /// iOS only. Does nothing, if called on Android. /// /// On iOS 14.5+, after requesting the app tracking entitlement using ATT, you need to notify Qonversion if tracking is allowed and IDFA is available. diff --git a/macos/qonversion_flutter.podspec b/macos/qonversion_flutter.podspec index 43fabfc7..1e645b50 100644 --- a/macos/qonversion_flutter.podspec +++ b/macos/qonversion_flutter.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' s.platform = :osx, '10.12' - s.dependency "QonversionSandwich", "7.8.0" + s.dependency "QonversionSandwich", "7.9.0" s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' From b32aeac2c8f35b3ae0c1559fcc764a46c2500163 Mon Sep 17 00:00:00 2001 From: Surik Date: Thu, 30 Apr 2026 00:12:29 +0300 Subject: [PATCH 2/2] chore: remove unrelated Unity prompt doc accidentally added --- NOCODES_UNITY_IMPLEMENTATION_PROMPT.md | 375 ------------------------- 1 file changed, 375 deletions(-) delete mode 100644 NOCODES_UNITY_IMPLEMENTATION_PROMPT.md diff --git a/NOCODES_UNITY_IMPLEMENTATION_PROMPT.md b/NOCODES_UNITY_IMPLEMENTATION_PROMPT.md deleted file mode 100644 index 7ed630d5..00000000 --- a/NOCODES_UNITY_IMPLEMENTATION_PROMPT.md +++ /dev/null @@ -1,375 +0,0 @@ -# Промпт для реализации NoCodes в Unity SDK - -## Контекст -В существующем Unity SDK для Qonversion есть только основная функциональность Qonversion (инициализация, покупки, entitlements и т.д.), но отсутствует модуль NoCodes. Необходимо реализовать NoCodes функциональность, аналогичную той, что реализована во Flutter SDK. - -## Архитектура и требования - -### 1. Основной API класс `NoCodes` - -Создать статический класс `NoCodes` с паттерном Singleton, аналогичный Flutter реализации: - -**Основные методы:** -- `Initialize(NoCodesConfig config)` - инициализация NoCodes SDK с конфигурацией -- `InitializeWithProjectKey(string projectKey)` - упрощенная инициализация только с project key (для обратной совместимости) -- `GetSharedInstance()` - получение текущего инициализированного экземпляра (throws exception если не инициализирован) - -**События (Events/Callbacks):** -Реализовать систему событий для 6 типов событий NoCodes: -- `ScreenShown` - событие когда NoCodes экран показан -- `Finished` - событие когда NoCodes flow завершен -- `ActionStarted` - событие когда действие начато -- `ActionFailed` - событие когда действие провалилось -- `ActionFinished` - событие когда действие завершено -- `ScreenFailedToLoad` - событие когда экран не смог загрузиться - -**Методы управления экраном:** -- `SetScreenPresentationConfig(NoCodesPresentationConfig config, string contextKey = null)` - установка конфигурации презентации экрана -- `ShowScreen(string contextKey)` - показать NoCodes экран с указанным context key -- `Close()` - закрыть текущий NoCodes экран - -### 2. Классы конфигурации - -**NoCodesConfig:** -```csharp -public class NoCodesConfig -{ - public string ProjectKey { get; } - public string ProxyUrl { get; } - - public NoCodesConfig(string projectKey, string proxyUrl = null) - { - ProjectKey = projectKey; - ProxyUrl = proxyUrl; - } -} -``` - -**NoCodesConfigBuilder:** -```csharp -public class NoCodesConfigBuilder -{ - private readonly string _projectKey; - private string _proxyUrl; - - public NoCodesConfigBuilder(string projectKey) - { - _projectKey = projectKey; - } - - public NoCodesConfigBuilder SetProxyUrl(string proxyUrl) - { - _proxyUrl = proxyUrl; - return this; - } - - public NoCodesConfig Build() - { - return new NoCodesConfig(_projectKey, _proxyUrl); - } -} -``` - -**NoCodesPresentationConfig:** -```csharp -public enum NoCodesPresentationStyle -{ - Push, - FullScreen, - Popover -} - -public class NoCodesPresentationConfig -{ - public bool Animated { get; } - public NoCodesPresentationStyle PresentationStyle { get; } - - public NoCodesPresentationConfig(bool animated = true, - NoCodesPresentationStyle presentationStyle = NoCodesPresentationStyle.FullScreen) - { - Animated = animated; - PresentationStyle = presentationStyle; - } - - // Метод для конвертации в Dictionary для передачи в нативный код - public Dictionary ToDictionary() - { - string styleString = PresentationStyle switch - { - NoCodesPresentationStyle.Push => "Push", - NoCodesPresentationStyle.FullScreen => "FullScreen", - NoCodesPresentationStyle.Popover => "Popover", - _ => "FullScreen" - }; - - return new Dictionary - { - { "animated", Animated }, - { "presentationStyle", styleString } - }; - } -} -``` - -### 3. Классы событий - -**Базовый класс события:** -```csharp -public abstract class NoCodesEvent -{ - public Dictionary Payload { get; } - - protected NoCodesEvent(Dictionary payload = null) - { - Payload = payload ?? new Dictionary(); - } -} -``` - -**Конкретные классы событий:** -```csharp -public class NoCodesScreenShownEvent : NoCodesEvent -{ - public NoCodesScreenShownEvent(Dictionary payload = null) - : base(payload) { } -} - -public class NoCodesFinishedEvent : NoCodesEvent -{ - public NoCodesFinishedEvent(Dictionary payload = null) - : base(payload) { } -} - -public class NoCodesActionStartedEvent : NoCodesEvent -{ - public NoCodesActionStartedEvent(Dictionary payload = null) - : base(payload) { } -} - -public class NoCodesActionFailedEvent : NoCodesEvent -{ - public NoCodesActionFailedEvent(Dictionary payload = null) - : base(payload) { } -} - -public class NoCodesActionFinishedEvent : NoCodesEvent -{ - public NoCodesActionFinishedEvent(Dictionary payload = null) - : base(payload) { } -} - -public class NoCodesScreenFailedToLoadEvent : NoCodesEvent -{ - public NoCodesScreenFailedToLoadEvent(Dictionary payload = null) - : base(payload) { } -} -``` - -### 4. Нативная интеграция - -**Android (Java/Kotlin):** -- Создать класс `NoCodesUnityPlugin` аналогичный `NoCodesPlugin` из Flutter SDK -- Использовать `NoCodesSandwich` из библиотеки `io.qonversion:sandwich` -- Реализовать `NoCodesEventListener` для обработки событий -- Методы для вызова из Unity: - - `initializeNoCodes(String projectKey, String version, String source, String proxyUrl)` - - `setScreenPresentationConfig(Map config, String contextKey)` - - `showNoCodesScreen(String contextKey)` - - `closeNoCodes()` -- Отправка событий в Unity через UnitySendMessage для каждого типа события - -**iOS (Objective-C/Swift):** -- Создать класс `NoCodesUnityPlugin` аналогичный `NoCodesPlugin` из Flutter SDK -- Использовать `NoCodesSandwich` из QonversionSandwich framework -- Реализовать `NoCodesEventListener` протокол -- Методы для вызова из Unity: - - `initializeNoCodes(projectKey:version:source:proxyUrl:)` - - `setScreenPresentationConfig(_:forContextKey:)` - - `showScreen(_:)` - - `close()` -- Отправка событий в Unity через UnitySendMessage для каждого типа события - -### 5. Константы и имена методов - -**Методы для MethodChannel/Unity:** -- `initializeNoCodes` - инициализация -- `setScreenPresentationConfig` - установка конфигурации презентации -- `showNoCodesScreen` - показать экран -- `closeNoCodes` - закрыть экран - -**Имена событий для передачи в Unity:** -- `nocodes_screen_shown` -- `nocodes_finished` -- `nocodes_action_started` -- `nocodes_action_failed` -- `nocodes_action_finished` -- `nocodes_screen_failed_to_load` - -**Параметры:** -- `projectKey` - ключ проекта Qonversion -- `version` - версия SDK (например, "10.0.3") -- `source` - источник SDK (например, "unity") -- `proxyUrl` - опциональный URL прокси -- `config` - конфигурация презентации (Dictionary с `animated` и `presentationStyle`) -- `contextKey` - ключ контекста для показа экрана -- `payload` - данные события (Dictionary) - -### 6. Особенности реализации - -**Платформенная поддержка:** -- ✅ iOS: Полная поддержка -- ✅ Android: Полная поддержка -- ❌ Другие платформы: No-op методы (ничего не делают, не выбрасывают ошибки) - -**Обработка ошибок:** -- Все методы должны быть безопасными и не выбрасывать исключения в Unity -- При ошибках инициализации логировать ошибку, но не крашить приложение -- События должны обрабатываться асинхронно - -**Потокобезопасность:** -- Singleton должен быть потокобезопасным -- События должны обрабатываться на главном потоке Unity - -### 7. Пример использования - -```csharp -using Qonversion.Unity; - -public class NoCodesExample : MonoBehaviour -{ - void Start() - { - // Инициализация через builder - var config = new NoCodesConfigBuilder("your_project_key") - .SetProxyUrl("https://proxy.example.com") - .Build(); - - NoCodes.Initialize(config); - - // Подписка на события - NoCodes.ScreenShown += OnScreenShown; - NoCodes.Finished += OnFinished; - NoCodes.ActionStarted += OnActionStarted; - NoCodes.ActionFailed += OnActionFailed; - NoCodes.ActionFinished += OnActionFinished; - NoCodes.ScreenFailedToLoad += OnScreenFailedToLoad; - } - - void OnScreenShown(NoCodesScreenShownEvent evt) - { - Debug.Log($"Screen shown with payload: {JsonUtility.ToJson(evt.Payload)}"); - } - - void OnFinished(NoCodesFinishedEvent evt) - { - Debug.Log($"Finished with payload: {JsonUtility.ToJson(evt.Payload)}"); - } - - // ... другие обработчики событий - - public async void ShowNoCodesScreen() - { - // Установка конфигурации презентации - var presentationConfig = new NoCodesPresentationConfig( - animated: true, - presentationStyle: NoCodesPresentationStyle.FullScreen - ); - - await NoCodes.GetSharedInstance().SetScreenPresentationConfig( - presentationConfig, - contextKey: "my_context_key" - ); - - // Показ экрана - await NoCodes.GetSharedInstance().ShowScreen("my_context_key"); - } - - public async void CloseNoCodesScreen() - { - await NoCodes.GetSharedInstance().Close(); - } - - void OnDestroy() - { - // Отписка от событий - NoCodes.ScreenShown -= OnScreenShown; - NoCodes.Finished -= OnFinished; - // ... остальные отписки - } -} -``` - -### 8. Структура файлов - -``` -QonversionUnity/ -├── Runtime/ -│ ├── NoCodes/ -│ │ ├── NoCodes.cs (основной API класс) -│ │ ├── NoCodesConfig.cs -│ │ ├── NoCodesConfigBuilder.cs -│ │ ├── NoCodesPresentationConfig.cs -│ │ ├── Events/ -│ │ │ ├── NoCodesEvent.cs (базовый класс) -│ │ │ ├── NoCodesScreenShownEvent.cs -│ │ │ ├── NoCodesFinishedEvent.cs -│ │ │ ├── NoCodesActionStartedEvent.cs -│ │ │ ├── NoCodesActionFailedEvent.cs -│ │ │ ├── NoCodesActionFinishedEvent.cs -│ │ │ └── NoCodesScreenFailedToLoadEvent.cs -│ │ └── Internal/ -│ │ └── NoCodesInternal.cs (внутренняя реализация) -├── Plugins/ -│ ├── Android/ -│ │ └── NoCodesUnityPlugin.java (или .kt) -│ └── iOS/ -│ └── NoCodesUnityPlugin.m (или .swift) -``` - -### 9. Зависимости - -**Android:** -- Использовать существующую зависимость `io.qonversion:sandwich` (уже должна быть в проекте для основного Qonversion SDK) -- `NoCodesSandwich` класс из этой библиотеки - -**iOS:** -- Использовать существующий QonversionSandwich framework (уже должен быть в проекте) -- `NoCodesSandwich` класс из этого framework - -### 10. Тестирование - -Реализовать: -- Unit тесты для C# классов -- Интеграционные тесты для нативной интеграции -- Пример сцены в Unity с UI для демонстрации функциональности - -### 11. Документация - -Добавить: -- XML комментарии для всех публичных методов и классов -- README с примерами использования -- Миграционный гайд для пользователей, переходящих с других SDK - -## Важные замечания - -1. **Совместимость**: Реализация должна быть совместима с существующим Qonversion SDK в Unity и не ломать существующую функциональность - -2. **Асинхронность**: Все методы, которые вызывают нативный код, должны быть асинхронными (async/await) - -3. **События**: Использовать C# события (events) для подписки на события NoCodes, аналогично тому как это сделано в основном Qonversion SDK - -4. **JSON сериализация**: Использовать Unity JsonUtility или Newtonsoft.Json для сериализации/десериализации payload событий - -5. **Версионирование**: Версия SDK должна передаваться в нативный код при инициализации (например, "10.0.3") - -6. **Source идентификация**: При инициализации передавать `source: "unity"` для идентификации SDK в аналитике Qonversion - -## Референсная реализация - -Для понимания деталей реализации можно использовать: -- Flutter SDK: `/lib/src/nocodes.dart` и связанные файлы -- Android нативная часть: `/android/src/main/kotlin/.../NoCodesPlugin.kt` -- iOS нативная часть: `/ios/Classes/NoCodesPlugin.swift` - -Все эти файлы содержат полную реализацию, которую можно адаптировать для Unity. -