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 android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version '4.1.1'

buildscript {
ext.kotlin_version = '1.3.50'
ext.qonversion_version = '3.1.1'
ext.qonversion_version = '3.2.2'
repositories {
google()
jcenter()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.qonversion.flutter.sdk.qonversion_flutter_sdk

import com.google.gson.Gson
import com.qonversion.android.sdk.automations.Automations
import com.qonversion.android.sdk.automations.AutomationsDelegate
import com.qonversion.android.sdk.automations.QActionResult
import io.flutter.plugin.common.BinaryMessenger

class AutomationsPlugin {
private var shownScreensStreamHandler: BaseEventStreamHandler? = null
private var startedActionsStreamHandler: BaseEventStreamHandler? = null
private var failedActionsStreamHandler: BaseEventStreamHandler? = null
private var finishedActionsStreamHandler: BaseEventStreamHandler? = null
private var finishedAutomationsStreamHandler: BaseEventStreamHandler? = null
private val automationsDelegate = getAutomationsDelegate()

companion object {
private const val EVENT_CHANNEL_SHOWN_SCREENS = "shown_screens"
private const val EVENT_CHANNEL_STARTED_ACTIONS = "started_actions"
private const val EVENT_CHANNEL_FAILED_ACTIONS = "failed_actions"
private const val EVENT_CHANNEL_FINISHED_ACTIONS = "finished_actions"
private const val EVENT_CHANNEL_FINISHED_AUTOMATIONS = "finished_automations"
}

fun register(messenger: BinaryMessenger) {
val shownScreensListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_SHOWN_SCREENS)
shownScreensListener.register()
shownScreensStreamHandler = shownScreensListener.eventStreamHandler

val startedActionsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_STARTED_ACTIONS)
startedActionsListener.register()
startedActionsStreamHandler = startedActionsListener.eventStreamHandler

val failedActionsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_FAILED_ACTIONS)
failedActionsListener.register()
failedActionsStreamHandler = failedActionsListener.eventStreamHandler

val finishedActionsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_FINISHED_ACTIONS)
finishedActionsListener.register()
finishedActionsStreamHandler = finishedActionsListener.eventStreamHandler

val finishedAutomationsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_FINISHED_AUTOMATIONS)
finishedAutomationsListener.register()
finishedAutomationsStreamHandler = finishedAutomationsListener.eventStreamHandler
}

fun setAutomationsDelegate() {
Automations.setDelegate(automationsDelegate)
}

private fun getAutomationsDelegate() = object : AutomationsDelegate {
override fun automationsDidShowScreen(screenId: String) {
shownScreensStreamHandler?.eventSink?.success(screenId)
}

override fun automationsDidStartExecuting(actionResult: QActionResult) {
val payload = Gson().toJson(actionResult.toMap())
startedActionsStreamHandler?.eventSink?.success(payload)
}

override fun automationsDidFailExecuting(actionResult: QActionResult) {
val payload = Gson().toJson(actionResult.toMap())
failedActionsStreamHandler?.eventSink?.success(payload)
}

override fun automationsDidFinishExecuting(actionResult: QActionResult) {
val payload = Gson().toJson(actionResult.toMap())
finishedActionsStreamHandler?.eventSink?.success(payload)
}

override fun automationsFinished() {
finishedAutomationsStreamHandler?.eventSink?.success(null)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken
import com.qonversion.android.sdk.QonversionError
import com.qonversion.android.sdk.QonversionErrorCode
import com.qonversion.android.sdk.automations.AutomationsEvent
import com.qonversion.android.sdk.automations.AutomationsEventType
import com.qonversion.android.sdk.automations.QActionResult
import com.qonversion.android.sdk.automations.QActionResultType
import com.qonversion.android.sdk.dto.QLaunchResult
import com.qonversion.android.sdk.dto.QPermission
import com.qonversion.android.sdk.dto.eligibility.QEligibility
Expand Down Expand Up @@ -151,3 +155,48 @@ fun mapQProduct(jsonProduct: String): QProduct? {
it.trialDuration = productTrialDuration
}
}

fun QActionResult.toMap(): Map<String, Any?> {
return mapOf("action_type" to type.toInt(),
"parameters" to value,
"error" to error.toMap())
}

fun QActionResultType.toInt(): Int {
return when (this) {
QActionResultType.Unknown -> 0
QActionResultType.Url -> 1
QActionResultType.DeepLink -> 2
QActionResultType.Navigation -> 3
QActionResultType.Purchase -> 4
QActionResultType.Restore -> 5
QActionResultType.Close -> 6
}
}

fun AutomationsEvent.toMap(): Map<String, Any?> {
return mapOf("event_type" to type.toInt(),
"date" to date.time.toDouble())
}

fun AutomationsEventType.toInt(): Int {
return when (this) {
AutomationsEventType.Unknown -> 0
AutomationsEventType.TrialStarted -> 1
AutomationsEventType.TrialConverted -> 2
AutomationsEventType.TrialCanceled -> 3
AutomationsEventType.TrialBillingRetry -> 4
AutomationsEventType.SubscriptionStarted -> 5
AutomationsEventType.SubscriptionRenewed -> 6
AutomationsEventType.SubscriptionRefunded -> 7
AutomationsEventType.SubscriptionCanceled -> 8
AutomationsEventType.SubscriptionBillingRetry -> 9
AutomationsEventType.InAppPurchase -> 10
AutomationsEventType.SubscriptionUpgraded -> 11
AutomationsEventType.TrialStillActive -> 12
AutomationsEventType.TrialExpired -> 13
AutomationsEventType.SubscriptionExpired -> 14
AutomationsEventType.SubscriptionDowngraded -> 15
AutomationsEventType.SubscriptionProductChanged -> 16
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package com.qonversion.flutter.sdk.qonversion_flutter_sdk

import android.app.Activity
import android.app.Application
import android.preference.PreferenceManager
import android.util.Log
import androidx.annotation.NonNull
import androidx.preference.PreferenceManager
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.qonversion.android.sdk.*
Expand All @@ -30,6 +30,7 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
private var application: Application? = null
private var channel: MethodChannel? = null
private var deferredPurchasesStreamHandler: BaseEventStreamHandler? = null
private var automationsPlugin: AutomationsPlugin? = null

companion object {
const val METHOD_CHANNEL = "qonversion_flutter_sdk"
Expand Down Expand Up @@ -115,6 +116,8 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
"checkTrialIntroEligibility" -> checkTrialIntroEligibility(args, result)
"storeSdkInfo" -> storeSdkInfo(args, result)
"identify" -> identify(args["userId"] as? String, result)
"setNotificationsToken" -> setNotificationsToken(args["notificationsToken"] as? String, result)
"handleNotification" -> handleNotification(args, result)
else -> result.notImplemented()
}
}
Expand All @@ -138,7 +141,9 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
}
)
startListeningPendingPurchasesEvents()
} ?: result.error(QonversionErrorCode.UnknownError.name, "Couldn't launch Qonversion. There is no Application context", null)
automationsPlugin?.setAutomationsDelegate()
}
?: result.error(QonversionErrorCode.UnknownError.name, "Couldn't launch Qonversion. There is no Application context", null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct indent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a way like Android Studio takes care of the formatting code.

}

private fun identify(userId: String?, result: Result) {
Expand Down Expand Up @@ -347,6 +352,25 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
})
}

private fun setNotificationsToken(token: String?, result: Result) {
token?.let {
Qonversion.setNotificationsToken(it)
result.success(null)
} ?: result.noArgsError()
}

private fun handleNotification(args: Map<String, Any>, result: Result) {
val data = args["notificationData"] as? Map<String, Any> ?: return result.noDataError()

if (data.isEmpty()) {
return result.noDataError()
}

val stringsMap: Map<String, String> = data.mapValues { it.value.toString() }
val isQonversionNotification = Qonversion.handleNotification(stringsMap)
result.success(isQonversionNotification)
}

private fun getPurchasesListener(result: Result) = object : QonversionPermissionsCallback {
override fun onSuccess(permissions: Map<String, QPermission>) =
result.success(PurchaseResult(permissions).toMap())
Expand Down Expand Up @@ -414,6 +438,9 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
// 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().apply {
register(messenger)
}
}

private fun tearDown() {
Expand Down
66 changes: 66 additions & 0 deletions ios/Classes/AutomationsPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// AutomationsPlugin.swift
// qonversion_flutter
//
// Created by Maria on 18.11.2021.
//

import Qonversion

public class AutomationsPlugin: NSObject {
private let eventChannelShownScreens = "shown_screens"
private let eventChannelStartedActions = "started_actions"
private let eventChannelFailedActions = "failed_actions"
private let eventChannelFinishedActions = "finished_actions"
private let eventChannelFinishedAutomations = "finished_automations"

private var shownScreensStreamHandler: BaseEventStreamHandler?
private var startedActionsStreamHandler: BaseEventStreamHandler?
private var failedActionsStreamHandler: BaseEventStreamHandler?
private var finishedActionsStreamHandler: BaseEventStreamHandler?
private var finishedAutomationsStreamHandler: BaseEventStreamHandler?

public func register(_ registrar: FlutterPluginRegistrar) {
let shownScreensListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelShownScreens)
shownScreensListener.register() { self.shownScreensStreamHandler = $0 }

let startedActionsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelStartedActions)
startedActionsListener.register() { self.startedActionsStreamHandler = $0 }

let failedActionsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelFailedActions)
failedActionsListener.register() { self.failedActionsStreamHandler = $0 }

let finishedActionsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelFinishedActions)
finishedActionsListener.register() { self.finishedActionsStreamHandler = $0 }

let finishedAutomationsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelFinishedAutomations)
finishedAutomationsListener.register() { self.finishedAutomationsStreamHandler = $0 }

Qonversion.Automations.setDelegate(self)
}
}

extension AutomationsPlugin: Qonversion.AutomationsDelegate {
public func automationsDidShowScreen(_ screenID: String) {
shownScreensStreamHandler?.eventSink?(screenID)
}

public func automationsDidStartExecuting(actionResult: Qonversion.ActionResult) {
let payload = actionResult.toMap().toJson()
startedActionsStreamHandler?.eventSink?(payload)
}

public func automationsDidFailExecuting(actionResult: Qonversion.ActionResult) {
let payload = actionResult.toMap().toJson()
failedActionsStreamHandler?.eventSink?(payload)
}

public func automationsDidFinishExecuting(actionResult: Qonversion.ActionResult) {
let payload = actionResult.toMap().toJson()
finishedActionsStreamHandler?.eventSink?(payload)
}

public func automationsFinished() {
finishedAutomationsStreamHandler?.eventSink?(nil)
}
}
41 changes: 40 additions & 1 deletion ios/Classes/Mapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ struct PurchaseResult {
"is_cancelled": isCancelled,
]
}

}

extension NSError {
Expand All @@ -37,6 +36,30 @@ extension NSError {
}
}

extension Date {
func toMilliseconds() -> Double {
return timeIntervalSince1970 * 1000
}
}

extension String {
func toData() -> Data {
let len = count / 2
var data = Data(capacity: len)
var i = startIndex
for _ in 0..<len {
let j = index(i, offsetBy: 2)
let bytes = self[i..<j]
if var num = UInt8(bytes, radix: 16) {
data.append(&num, count: 1)
}
i = j
}

return data
}
}

extension Qonversion.LaunchResult {
func toMap() -> [String: Any] {
return [
Expand Down Expand Up @@ -168,3 +191,19 @@ extension Dictionary {
}
}

extension Qonversion.ActionResult {
func toMap() -> [String: Any?] {
let nsError = error as NSError?

return ["action_type": type.rawValue,
"parameters": parameters,
"error": nsError?.toMap()]
}
}

extension QONAutomationsEvent {
func toMap() -> [String: Any?] {
return ["event_type": type.rawValue,
"date": date.toMilliseconds()]
}
}
Loading