Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: subosito/flutter-action@v1
name: Setup Flutter
with:
flutter-version: '3.7.7'
flutter-version: '3.32.4'
channel: 'stable'
- run: flutter pub get
- name: Validation
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 10.0.0
### Introducing Qonversion No-Codes Beta!

**Qonversion No-Codes** is a product designed to help you **build and customize** paywall screens **without writing code**.
It allows seamless integration of pre-built subscription UI components, enabling a faster and more flexible way to design paywalls directly within your app.
See more in the [documentation](https://documentation.qonversion.io/docs/getting-started-with-no-code-screens/).

With this update, we are **removing** deprecated **Automations**, so we encourage you to transition your paywalls to the new Qonversion No-Codes.

## 9.3.1
* Android and iOS stability improvements.

Expand Down
4 changes: 3 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
jcenter()
}

Expand All @@ -17,6 +18,7 @@ buildscript {
rootProject.allprojects {
repositories {
google()
mavenCentral()
jcenter()
mavenLocal()
}
Expand Down Expand Up @@ -51,6 +53,6 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "io.qonversion:sandwich:5.2.1"
implementation "io.qonversion:sandwich:6.0.8"
implementation 'com.google.code.gson:gson:2.9.0'
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.qonversion.flutter.sdk.qonversion_flutter_sdk

import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel.Result
import io.qonversion.sandwich.BridgeData
import io.qonversion.sandwich.NoCodesEventListener
import io.qonversion.sandwich.NoCodesSandwich
import com.google.gson.Gson

class NoCodesPlugin(private val messenger: BinaryMessenger, private val context: Context) : NoCodesEventListener {
private var noCodesSandwich: NoCodesSandwich? = null
private val gson = Gson()

// Separate event stream handlers for each event type
private var screenShownEventStreamHandler: BaseEventStreamHandler? = null
private var finishedEventStreamHandler: BaseEventStreamHandler? = null
private var actionStartedEventStreamHandler: BaseEventStreamHandler? = null
private var actionFailedEventStreamHandler: BaseEventStreamHandler? = null
private var actionFinishedEventStreamHandler: BaseEventStreamHandler? = null
private var screenFailedToLoadEventStreamHandler: BaseEventStreamHandler? = null

companion object {
private const val SCREEN_SHOWN_EVENT_CHANNEL = "nocodes_screen_shown"
private const val FINISHED_EVENT_CHANNEL = "nocodes_finished"
private const val ACTION_STARTED_EVENT_CHANNEL = "nocodes_action_started"
private const val ACTION_FAILED_EVENT_CHANNEL = "nocodes_action_failed"
private const val ACTION_FINISHED_EVENT_CHANNEL = "nocodes_action_finished"
private const val SCREEN_FAILED_TO_LOAD_EVENT_CHANNEL = "nocodes_screen_failed_to_load"
}

init {
setup()
}

private fun setup() {
// Register separate event channels for each event type
val screenShownListener = BaseListenerWrapper(messenger, SCREEN_SHOWN_EVENT_CHANNEL)
screenShownListener.register()
this.screenShownEventStreamHandler = screenShownListener.eventStreamHandler

val finishedListener = BaseListenerWrapper(messenger, FINISHED_EVENT_CHANNEL)
finishedListener.register()
this.finishedEventStreamHandler = finishedListener.eventStreamHandler

val actionStartedListener = BaseListenerWrapper(messenger, ACTION_STARTED_EVENT_CHANNEL)
actionStartedListener.register()
this.actionStartedEventStreamHandler = actionStartedListener.eventStreamHandler

val actionFailedListener = BaseListenerWrapper(messenger, ACTION_FAILED_EVENT_CHANNEL)
actionFailedListener.register()
this.actionFailedEventStreamHandler = actionFailedListener.eventStreamHandler

val actionFinishedListener = BaseListenerWrapper(messenger, ACTION_FINISHED_EVENT_CHANNEL)
actionFinishedListener.register()
this.actionFinishedEventStreamHandler = actionFinishedListener.eventStreamHandler

val screenFailedToLoadListener = BaseListenerWrapper(messenger, SCREEN_FAILED_TO_LOAD_EVENT_CHANNEL)
screenFailedToLoadListener.register()
this.screenFailedToLoadEventStreamHandler = screenFailedToLoadListener.eventStreamHandler
}

fun initializeNoCodes(projectKey: String, result: Result) {
if (projectKey.isNotEmpty()) {
// Initialize NoCodes Sandwich
noCodesSandwich = NoCodesSandwich()
noCodesSandwich?.initialize(context, projectKey)
noCodesSandwich?.setDelegate(this)
result.success(null)
} else {
result.noNecessaryDataError()
}
}

fun setScreenPresentationConfig(config: Map<String, Any>?, contextKey: String?, result: Result) {
if (config != null) {
noCodesSandwich?.setScreenPresentationConfig(config, contextKey)
result.success(null)
} else {
result.noNecessaryDataError()
}
}

fun showNoCodesScreen(contextKey: String?, result: Result) {
if (contextKey != null) {
noCodesSandwich?.showScreen(contextKey)
result.success(null)
} else {
result.noNecessaryDataError()
}
}

fun closeNoCodes(result: Result) {
noCodesSandwich?.close()
result.success(null)
}

// NoCodesEventListener implementation
override fun onNoCodesEvent(event: NoCodesEventListener.Event, payload: BridgeData?) {
val eventData = mapOf("payload" to (payload ?: emptyMap<String, Any>()))

// Convert to JSON string
val jsonString = gson.toJson(eventData)

when (event) {
NoCodesEventListener.Event.ScreenShown -> {
screenShownEventStreamHandler?.eventSink?.success(jsonString)
}
NoCodesEventListener.Event.Finished -> {
finishedEventStreamHandler?.eventSink?.success(jsonString)
}
NoCodesEventListener.Event.ActionStarted -> {
actionStartedEventStreamHandler?.eventSink?.success(jsonString)
}
NoCodesEventListener.Event.ActionFailed -> {
actionFailedEventStreamHandler?.eventSink?.success(jsonString)
}
NoCodesEventListener.Event.ActionFinished -> {
actionFinishedEventStreamHandler?.eventSink?.success(jsonString)
}
NoCodesEventListener.Event.ScreenFailedToLoad -> {
screenFailedToLoadEventStreamHandler?.eventSink?.success(jsonString)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import io.qonversion.sandwich.QonversionEventsListener
import io.qonversion.sandwich.QonversionSandwich

class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
private var activity: Activity? = null
private var application: Application? = null
private var activity: Activity? = null
private var channel: MethodChannel? = null
private var updatedEntitlementsStreamHandler: BaseEventStreamHandler? = null
private var noCodesPlugin: NoCodesPlugin? = null

private val qonversionSandwich by lazy {
application?.let {
application?.let {
QonversionSandwich(
it,
object : ActivityProvider {
Expand All @@ -35,8 +36,6 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
} ?: throw IllegalStateException("Failed to initialize Qonversion Sandwich. Application is null.")
}

private lateinit var automationsPlugin: AutomationsPlugin

private val qonversionEventsListener: QonversionEventsListener = object : QonversionEventsListener {
override fun onEntitlementsUpdated(entitlements: BridgeData) {
val payload = Gson().toJson(entitlements)
Expand Down Expand Up @@ -109,12 +108,10 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
"isFallbackFileAccessible" -> {
return isFallbackFileAccessible(result)
}
"automationsSubscribe" -> {
return automationsPlugin.subscribe()
}
"remoteConfigList" -> {
return remoteConfigList(result)
}
"closeNoCodes" -> noCodesPlugin?.closeNoCodes(result)
}

// Methods with args
Expand All @@ -135,15 +132,10 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
"detachUserFromRemoteConfiguration" -> detachUserFromRemoteConfiguration(args, result)
"storeSdkInfo" -> storeSdkInfo(args, result)
"identify" -> identify(args["userId"] as? String, result)
"automationsSetNotificationsToken" -> automationsPlugin.setNotificationsToken(args["notificationsToken"] as? String, result)
"automationsHandleNotification" -> automationsPlugin.handleNotification(args, result)
"automationsGetNotificationCustomPayload" -> automationsPlugin.getNotificationCustomPayload(args, result)
"automationsShowScreen" -> automationsPlugin.showScreen(args["screenId"] as? String, result)
"setScreenPresentationConfig" -> automationsPlugin.setScreenPresentationConfig(
args["configData"] as? Map<String, Any>,
args["screenId"] as? String,
result
)
// NoCodes methods
"initializeNoCodes" -> noCodesPlugin?.initializeNoCodes(args["projectKey"] as? String ?: "", result)
"setScreenPresentationConfig" -> noCodesPlugin?.setScreenPresentationConfig(args["config"] as? Map<String, Any>, args["contextKey"] as? String, result)
"showNoCodesScreen" -> noCodesPlugin?.showNoCodesScreen(args["contextKey"] as? String, result)
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -317,6 +309,13 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
channel = MethodChannel(messenger, METHOD_CHANNEL)
channel?.setMethodCallHandler(this)

// Register NoCodes plugin
try {
noCodesPlugin = NoCodesPlugin(messenger, application)
} catch (e: Exception) {
println("Failed to initialize NoCodesPlugin: ${e.message}")
}

// Register entitlements update events
val updatedEntitlementsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_UPDATED_ENTITLEMENTS)
updatedEntitlementsListener.register()
Expand All @@ -325,14 +324,13 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
// Register promo purchases events. Android SDK does not generate any promo purchases yet
val promoPurchasesListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_PROMO_PURCHASES)
promoPurchasesListener.register()

automationsPlugin = AutomationsPlugin(messenger)
}

private fun tearDown() {
channel?.setMethodCallHandler(null)
channel = null
this.updatedEntitlementsStreamHandler = null
this.noCodesPlugin = null
this.application = null
}
}
Loading