Skip to content

Commit

Permalink
Merge pull request #124 from qonversion/release/4.3.0
Browse files Browse the repository at this point in the history
Release 4.3.0
  • Loading branch information
Maria-Bordunova authored Nov 25, 2021
2 parents 763f179 + 0a78d85 commit 1578b75
Show file tree
Hide file tree
Showing 31 changed files with 772 additions and 58 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 4.3.0
* Qonversion Automation allows sending automated, personalized push notifications and in-app messages initiated by in-app purchase events.
This feature is designed to increase your app's revenue and retention, provide cancellation insights, reduce subscriber churn, and improve your subscribers' user experience.
See more in the [documentation](https://documentation.qonversion.io/docs/automations).

## 4.2.0
* Add `setAppleSearchAdsAttributionEnabled()` method

Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
group 'com.qonversion.flutter.sdk.qonversion_flutter_sdk'
version '4.1.1'
version '4.2.0'

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)
}

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

0 comments on commit 1578b75

Please sign in to comment.