Skip to content

Commit 1578b75

Browse files
Merge pull request #124 from qonversion/release/4.3.0
Release 4.3.0
2 parents 763f179 + 0a78d85 commit 1578b75

31 files changed

+772
-58
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 4.3.0
2+
* Qonversion Automation allows sending automated, personalized push notifications and in-app messages initiated by in-app purchase events.
3+
This feature is designed to increase your app's revenue and retention, provide cancellation insights, reduce subscriber churn, and improve your subscribers' user experience.
4+
See more in the [documentation](https://documentation.qonversion.io/docs/automations).
5+
16
## 4.2.0
27
* Add `setAppleSearchAdsAttributionEnabled()` method
38

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
group 'com.qonversion.flutter.sdk.qonversion_flutter_sdk'
2-
version '4.1.1'
2+
version '4.2.0'
33

44
buildscript {
55
ext.kotlin_version = '1.3.50'
6-
ext.qonversion_version = '3.1.1'
6+
ext.qonversion_version = '3.2.2'
77
repositories {
88
google()
99
jcenter()
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.qonversion.flutter.sdk.qonversion_flutter_sdk
2+
3+
import com.google.gson.Gson
4+
import com.qonversion.android.sdk.automations.Automations
5+
import com.qonversion.android.sdk.automations.AutomationsDelegate
6+
import com.qonversion.android.sdk.automations.QActionResult
7+
import io.flutter.plugin.common.BinaryMessenger
8+
9+
class AutomationsPlugin {
10+
private var shownScreensStreamHandler: BaseEventStreamHandler? = null
11+
private var startedActionsStreamHandler: BaseEventStreamHandler? = null
12+
private var failedActionsStreamHandler: BaseEventStreamHandler? = null
13+
private var finishedActionsStreamHandler: BaseEventStreamHandler? = null
14+
private var finishedAutomationsStreamHandler: BaseEventStreamHandler? = null
15+
private val automationsDelegate = getAutomationsDelegate()
16+
17+
companion object {
18+
private const val EVENT_CHANNEL_SHOWN_SCREENS = "shown_screens"
19+
private const val EVENT_CHANNEL_STARTED_ACTIONS = "started_actions"
20+
private const val EVENT_CHANNEL_FAILED_ACTIONS = "failed_actions"
21+
private const val EVENT_CHANNEL_FINISHED_ACTIONS = "finished_actions"
22+
private const val EVENT_CHANNEL_FINISHED_AUTOMATIONS = "finished_automations"
23+
}
24+
25+
fun register(messenger: BinaryMessenger) {
26+
val shownScreensListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_SHOWN_SCREENS)
27+
shownScreensListener.register()
28+
shownScreensStreamHandler = shownScreensListener.eventStreamHandler
29+
30+
val startedActionsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_STARTED_ACTIONS)
31+
startedActionsListener.register()
32+
startedActionsStreamHandler = startedActionsListener.eventStreamHandler
33+
34+
val failedActionsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_FAILED_ACTIONS)
35+
failedActionsListener.register()
36+
failedActionsStreamHandler = failedActionsListener.eventStreamHandler
37+
38+
val finishedActionsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_FINISHED_ACTIONS)
39+
finishedActionsListener.register()
40+
finishedActionsStreamHandler = finishedActionsListener.eventStreamHandler
41+
42+
val finishedAutomationsListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_FINISHED_AUTOMATIONS)
43+
finishedAutomationsListener.register()
44+
finishedAutomationsStreamHandler = finishedAutomationsListener.eventStreamHandler
45+
}
46+
47+
fun setAutomationsDelegate() {
48+
Automations.setDelegate(automationsDelegate)
49+
}
50+
51+
private fun getAutomationsDelegate() = object : AutomationsDelegate {
52+
override fun automationsDidShowScreen(screenId: String) {
53+
shownScreensStreamHandler?.eventSink?.success(screenId)
54+
}
55+
56+
override fun automationsDidStartExecuting(actionResult: QActionResult) {
57+
val payload = Gson().toJson(actionResult.toMap())
58+
startedActionsStreamHandler?.eventSink?.success(payload)
59+
}
60+
61+
override fun automationsDidFailExecuting(actionResult: QActionResult) {
62+
val payload = Gson().toJson(actionResult.toMap())
63+
failedActionsStreamHandler?.eventSink?.success(payload)
64+
}
65+
66+
override fun automationsDidFinishExecuting(actionResult: QActionResult) {
67+
val payload = Gson().toJson(actionResult.toMap())
68+
finishedActionsStreamHandler?.eventSink?.success(payload)
69+
}
70+
71+
override fun automationsFinished() {
72+
finishedAutomationsStreamHandler?.eventSink?.success(null)
73+
}
74+
}
75+
}

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/Mapper.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import com.google.gson.JsonSyntaxException
66
import com.google.gson.reflect.TypeToken
77
import com.qonversion.android.sdk.QonversionError
88
import com.qonversion.android.sdk.QonversionErrorCode
9+
import com.qonversion.android.sdk.automations.AutomationsEvent
10+
import com.qonversion.android.sdk.automations.AutomationsEventType
11+
import com.qonversion.android.sdk.automations.QActionResult
12+
import com.qonversion.android.sdk.automations.QActionResultType
913
import com.qonversion.android.sdk.dto.QLaunchResult
1014
import com.qonversion.android.sdk.dto.QPermission
1115
import com.qonversion.android.sdk.dto.eligibility.QEligibility
@@ -151,3 +155,48 @@ fun mapQProduct(jsonProduct: String): QProduct? {
151155
it.trialDuration = productTrialDuration
152156
}
153157
}
158+
159+
fun QActionResult.toMap(): Map<String, Any?> {
160+
return mapOf("action_type" to type.toInt(),
161+
"parameters" to value,
162+
"error" to error.toMap())
163+
}
164+
165+
fun QActionResultType.toInt(): Int {
166+
return when (this) {
167+
QActionResultType.Unknown -> 0
168+
QActionResultType.Url -> 1
169+
QActionResultType.DeepLink -> 2
170+
QActionResultType.Navigation -> 3
171+
QActionResultType.Purchase -> 4
172+
QActionResultType.Restore -> 5
173+
QActionResultType.Close -> 6
174+
}
175+
}
176+
177+
fun AutomationsEvent.toMap(): Map<String, Any?> {
178+
return mapOf("event_type" to type.toInt(),
179+
"date" to date.time.toDouble())
180+
}
181+
182+
fun AutomationsEventType.toInt(): Int {
183+
return when (this) {
184+
AutomationsEventType.Unknown -> 0
185+
AutomationsEventType.TrialStarted -> 1
186+
AutomationsEventType.TrialConverted -> 2
187+
AutomationsEventType.TrialCanceled -> 3
188+
AutomationsEventType.TrialBillingRetry -> 4
189+
AutomationsEventType.SubscriptionStarted -> 5
190+
AutomationsEventType.SubscriptionRenewed -> 6
191+
AutomationsEventType.SubscriptionRefunded -> 7
192+
AutomationsEventType.SubscriptionCanceled -> 8
193+
AutomationsEventType.SubscriptionBillingRetry -> 9
194+
AutomationsEventType.InAppPurchase -> 10
195+
AutomationsEventType.SubscriptionUpgraded -> 11
196+
AutomationsEventType.TrialStillActive -> 12
197+
AutomationsEventType.TrialExpired -> 13
198+
AutomationsEventType.SubscriptionExpired -> 14
199+
AutomationsEventType.SubscriptionDowngraded -> 15
200+
AutomationsEventType.SubscriptionProductChanged -> 16
201+
}
202+
}

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionFlutterSdkPlugin.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package com.qonversion.flutter.sdk.qonversion_flutter_sdk
22

33
import android.app.Activity
44
import android.app.Application
5+
import android.preference.PreferenceManager
56
import android.util.Log
67
import androidx.annotation.NonNull
7-
import androidx.preference.PreferenceManager
88
import com.google.gson.Gson
99
import com.google.gson.JsonSyntaxException
1010
import com.qonversion.android.sdk.*
@@ -30,6 +30,7 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
3030
private var application: Application? = null
3131
private var channel: MethodChannel? = null
3232
private var deferredPurchasesStreamHandler: BaseEventStreamHandler? = null
33+
private var automationsPlugin: AutomationsPlugin? = null
3334

3435
companion object {
3536
const val METHOD_CHANNEL = "qonversion_flutter_sdk"
@@ -115,6 +116,8 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
115116
"checkTrialIntroEligibility" -> checkTrialIntroEligibility(args, result)
116117
"storeSdkInfo" -> storeSdkInfo(args, result)
117118
"identify" -> identify(args["userId"] as? String, result)
119+
"setNotificationsToken" -> setNotificationsToken(args["notificationsToken"] as? String, result)
120+
"handleNotification" -> handleNotification(args, result)
118121
else -> result.notImplemented()
119122
}
120123
}
@@ -138,7 +141,9 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
138141
}
139142
)
140143
startListeningPendingPurchasesEvents()
141-
} ?: result.error(QonversionErrorCode.UnknownError.name, "Couldn't launch Qonversion. There is no Application context", null)
144+
automationsPlugin?.setAutomationsDelegate()
145+
}
146+
?: result.error(QonversionErrorCode.UnknownError.name, "Couldn't launch Qonversion. There is no Application context", null)
142147
}
143148

144149
private fun identify(userId: String?, result: Result) {
@@ -347,6 +352,25 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
347352
})
348353
}
349354

355+
private fun setNotificationsToken(token: String?, result: Result) {
356+
token?.let {
357+
Qonversion.setNotificationsToken(it)
358+
result.success(null)
359+
} ?: result.noArgsError()
360+
}
361+
362+
private fun handleNotification(args: Map<String, Any>, result: Result) {
363+
val data = args["notificationData"] as? Map<String, Any> ?: return result.noDataError()
364+
365+
if (data.isEmpty()) {
366+
return result.noDataError()
367+
}
368+
369+
val stringsMap: Map<String, String> = data.mapValues { it.value.toString() }
370+
val isQonversionNotification = Qonversion.handleNotification(stringsMap)
371+
result.success(isQonversionNotification)
372+
}
373+
350374
private fun getPurchasesListener(result: Result) = object : QonversionPermissionsCallback {
351375
override fun onSuccess(permissions: Map<String, QPermission>) =
352376
result.success(PurchaseResult(permissions).toMap())
@@ -414,6 +438,9 @@ class QonversionFlutterSdkPlugin : MethodCallHandler, FlutterPlugin, ActivityAwa
414438
// Register promo purchases events. Android SDK does not generate any promo purchases yet
415439
val promoPurchasesListener = BaseListenerWrapper(messenger, EVENT_CHANNEL_PROMO_PURCHASES)
416440
promoPurchasesListener.register()
441+
automationsPlugin = AutomationsPlugin().apply {
442+
register(messenger)
443+
}
417444
}
418445

419446
private fun tearDown() {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// AutomationsPlugin.swift
3+
// qonversion_flutter
4+
//
5+
// Created by Maria on 18.11.2021.
6+
//
7+
8+
import Qonversion
9+
10+
public class AutomationsPlugin: NSObject {
11+
private let eventChannelShownScreens = "shown_screens"
12+
private let eventChannelStartedActions = "started_actions"
13+
private let eventChannelFailedActions = "failed_actions"
14+
private let eventChannelFinishedActions = "finished_actions"
15+
private let eventChannelFinishedAutomations = "finished_automations"
16+
17+
private var shownScreensStreamHandler: BaseEventStreamHandler?
18+
private var startedActionsStreamHandler: BaseEventStreamHandler?
19+
private var failedActionsStreamHandler: BaseEventStreamHandler?
20+
private var finishedActionsStreamHandler: BaseEventStreamHandler?
21+
private var finishedAutomationsStreamHandler: BaseEventStreamHandler?
22+
23+
public func register(_ registrar: FlutterPluginRegistrar) {
24+
let shownScreensListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelShownScreens)
25+
shownScreensListener.register() { self.shownScreensStreamHandler = $0 }
26+
27+
let startedActionsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelStartedActions)
28+
startedActionsListener.register() { self.startedActionsStreamHandler = $0 }
29+
30+
let failedActionsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelFailedActions)
31+
failedActionsListener.register() { self.failedActionsStreamHandler = $0 }
32+
33+
let finishedActionsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelFinishedActions)
34+
finishedActionsListener.register() { self.finishedActionsStreamHandler = $0 }
35+
36+
let finishedAutomationsListener = FlutterListenerWrapper<BaseEventStreamHandler>(registrar, postfix: eventChannelFinishedAutomations)
37+
finishedAutomationsListener.register() { self.finishedAutomationsStreamHandler = $0 }
38+
39+
Qonversion.Automations.setDelegate(self)
40+
}
41+
}
42+
43+
extension AutomationsPlugin: Qonversion.AutomationsDelegate {
44+
public func automationsDidShowScreen(_ screenID: String) {
45+
shownScreensStreamHandler?.eventSink?(screenID)
46+
}
47+
48+
public func automationsDidStartExecuting(actionResult: Qonversion.ActionResult) {
49+
let payload = actionResult.toMap().toJson()
50+
startedActionsStreamHandler?.eventSink?(payload)
51+
}
52+
53+
public func automationsDidFailExecuting(actionResult: Qonversion.ActionResult) {
54+
let payload = actionResult.toMap().toJson()
55+
failedActionsStreamHandler?.eventSink?(payload)
56+
}
57+
58+
public func automationsDidFinishExecuting(actionResult: Qonversion.ActionResult) {
59+
let payload = actionResult.toMap().toJson()
60+
finishedActionsStreamHandler?.eventSink?(payload)
61+
}
62+
63+
public func automationsFinished() {
64+
finishedAutomationsStreamHandler?.eventSink?(nil)
65+
}
66+
}

ios/Classes/Mapper.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ struct PurchaseResult {
2323
"is_cancelled": isCancelled,
2424
]
2525
}
26-
2726
}
2827

2928
extension NSError {
@@ -37,6 +36,30 @@ extension NSError {
3736
}
3837
}
3938

39+
extension Date {
40+
func toMilliseconds() -> Double {
41+
return timeIntervalSince1970 * 1000
42+
}
43+
}
44+
45+
extension String {
46+
func toData() -> Data {
47+
let len = count / 2
48+
var data = Data(capacity: len)
49+
var i = startIndex
50+
for _ in 0..<len {
51+
let j = index(i, offsetBy: 2)
52+
let bytes = self[i..<j]
53+
if var num = UInt8(bytes, radix: 16) {
54+
data.append(&num, count: 1)
55+
}
56+
i = j
57+
}
58+
59+
return data
60+
}
61+
}
62+
4063
extension Qonversion.LaunchResult {
4164
func toMap() -> [String: Any] {
4265
return [
@@ -168,3 +191,19 @@ extension Dictionary {
168191
}
169192
}
170193

194+
extension Qonversion.ActionResult {
195+
func toMap() -> [String: Any?] {
196+
let nsError = error as NSError?
197+
198+
return ["action_type": type.rawValue,
199+
"parameters": parameters,
200+
"error": nsError?.toMap()]
201+
}
202+
}
203+
204+
extension QONAutomationsEvent {
205+
func toMap() -> [String: Any?] {
206+
return ["event_type": type.rawValue,
207+
"date": date.toMilliseconds()]
208+
}
209+
}

0 commit comments

Comments
 (0)