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
4 changes: 4 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ android {
jvmTarget = "1.8"
}
}

dependencies {
implementation "com.google.android.gms:play-services-ads-identifier:18.0.1"
}
1 change: 1 addition & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package="so.kontext.sdk.flutter">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />

<application />
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package so.kontext.sdk.flutter

import android.content.Context
import android.os.Handler
import android.os.Looper
import com.google.android.gms.ads.identifier.AdvertisingIdClient
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.Executors

class AdvertisingIdPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
private val mainHandler = Handler(Looper.getMainLooper())
private val executor = Executors.newSingleThreadExecutor()

override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
context = binding.applicationContext
channel = MethodChannel(binding.binaryMessenger, "kontext_flutter_sdk/advertising_id")
channel.setMethodCallHandler(this)
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getAdvertisingId" -> executor.execute {
val id = try {
val info = AdvertisingIdClient.getAdvertisingIdInfo(context)
if (info.isLimitAdTrackingEnabled) null else info.id
} catch (_: Exception) {
null
}

mainHandler.post {
result.success(id)
}
}

else -> result.notImplemented()
}
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
executor.shutdown()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,34 @@ package so.kontext.sdk.flutter
import io.flutter.embedding.engine.plugins.FlutterPlugin

class KontextSdkPlugin : FlutterPlugin {
private val advertisingId = AdvertisingIdPlugin()
private val appInfo = AppInfoPlugin()
private val audio = DeviceAudioPlugin()
private val hardware = DeviceHardwarePlugin()
private val network = DeviceNetworkPlugin()
private val os = OperationSystemPlugin()
private val power = DevicePowerPlugin()
private val audio = DeviceAudioPlugin()
private val network = DeviceNetworkPlugin()
private val tcf = TransparencyConsentFramework()

override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
advertisingId.onAttachedToEngine(binding)
appInfo.onAttachedToEngine(binding)
audio.onAttachedToEngine(binding)
hardware.onAttachedToEngine(binding)
network.onAttachedToEngine(binding)
os.onAttachedToEngine(binding)
power.onAttachedToEngine(binding)
audio.onAttachedToEngine(binding)
network.onAttachedToEngine(binding)
tcf.onAttachedToEngine(binding)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
advertisingId.onDetachedFromEngine(binding)
appInfo.onDetachedFromEngine(binding)
audio.onDetachedFromEngine(binding)
hardware.onDetachedFromEngine(binding)
network.onDetachedFromEngine(binding)
os.onDetachedFromEngine(binding)
power.onDetachedFromEngine(binding)
audio.onDetachedFromEngine(binding)
network.onDetachedFromEngine(binding)
tcf.onDetachedFromEngine(binding)
}
}
2 changes: 2 additions & 0 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver more relevant ads.</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
Expand Down
37 changes: 37 additions & 0 deletions ios/Classes/AdvertisingIdPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Flutter
import UIKit
import AdSupport
import AppTrackingTransparency

public class AdvertisingIdPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
name: "kontext_flutter_sdk/advertising_id",
binaryMessenger: registrar.messenger()
)
let instance = AdvertisingIdPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getAdvertisingId":
result(Self.getAdvertisingId())
default:
result(FlutterMethodNotImplemented)
}
}

private static func getAdvertisingId() -> String? {
let manager = ASIdentifierManager.shared()
if #available(iOS 14.0, *) {
return ATTrackingManager.trackingAuthorizationStatus == .authorized
? manager.advertisingIdentifier.uuidString
: nil
}

return manager.isAdvertisingTrackingEnabled
? manager.advertisingIdentifier.uuidString
: nil
}
}
8 changes: 5 additions & 3 deletions ios/Classes/KontextSdkPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import Flutter

public class KontextSdkPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
AdvertisingIdPlugin.register(with: registrar)
AppInfoPlugin.register(with: registrar)
DeviceHardwarePlugin.register(with: registrar)
OperationSystemPlugin.register(with: registrar)
DevicePowerPlugin.register(with: registrar)
DeviceAudioPlugin.register(with: registrar)
DeviceHardwarePlugin.register(with: registrar)
DeviceNetworkPlugin.register(with: registrar)
DevicePowerPlugin.register(with: registrar)
OperationSystemPlugin.register(with: registrar)
TrackingAuthorizationPlugin.register(with: registrar)
TransparencyConsentFrameworkPlugin.register(with: registrar)
}
}
73 changes: 73 additions & 0 deletions ios/Classes/TrackingAuthorizationPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Flutter
import UIKit
import AppTrackingTransparency

public class TrackingAuthorizationPlugin: NSObject, FlutterPlugin {
private var observer: NSObjectProtocol?
private static let notSupportedStatus = 4

deinit {
removeObserver()
}

public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
name: "kontext_flutter_sdk/tracking_authorization",
binaryMessenger: registrar.messenger()
)
let instance = TrackingAuthorizationPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getTrackingAuthorizationStatus":
getTrackingAuthorizationStatus(result: result)
case "requestTrackingAuthorization":
requestTrackingAuthorization(result: result)
default:
result(FlutterMethodNotImplemented)
}
}

private func getTrackingAuthorizationStatus(result: @escaping FlutterResult) {
if #available(iOS 14, *) {
result(Int(ATTrackingManager.trackingAuthorizationStatus.rawValue))
} else {
result(Self.notSupportedStatus)
}
}

private func requestTrackingAuthorization(result: @escaping FlutterResult) {
if #available(iOS 14, *) {
removeObserver()
ATTrackingManager.requestTrackingAuthorization { [weak self] status in
if status == .denied && ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
self?.addObserver(result: result)
return
}
result(Int(status.rawValue))
}
} else {
result(Self.notSupportedStatus)
}
}

private func addObserver(result: @escaping FlutterResult) {
removeObserver()
observer = NotificationCenter.default.addObserver(
forName: UIApplication.didBecomeActiveNotification,
object: nil,
queue: .main
) { [weak self] _ in
self?.requestTrackingAuthorization(result: result)
}
}

private func removeObserver() {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
self.observer = nil
}
}
}
21 changes: 18 additions & 3 deletions ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@
<plist version="1.0">
<dict>

<key>NSPrivacyTracking</key><false/>
<key>NSPrivacyTracking</key><true/>

<key>NSPrivacyTrackingDomains</key>
<array/>
<array>
<string>megabrain.co</string>
</array>

<key>NSPrivacyCollectedDataTypes</key>
<array>

<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeDeviceID</string>
<key>NSPrivacyCollectedDataTypeLinked</key><true/>
<key>NSPrivacyCollectedDataTypeTracking</key><true/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
</array>
</dict>

<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeOtherAppInfo</string>
Expand All @@ -30,6 +45,7 @@
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>

</array>

<key>NSPrivacyAccessedAPITypes</key>
Expand All @@ -42,7 +58,6 @@
<string>C617.1</string>
</array>
</dict>

<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
Expand Down
2 changes: 1 addition & 1 deletion ios/kontext_flutter_sdk.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Kontext Flutter SDK: sound status, app info, hardware, power, network, etc.
s.platform = :ios, '12.0'
s.swift_version = '5.0'

s.frameworks = 'AVFoundation', 'SystemConfiguration', 'CoreTelephony', 'WebKit'
s.frameworks = 'AVFoundation', 'SystemConfiguration', 'CoreTelephony', 'WebKit', 'AdSupport', 'AppTrackingTransparency'

s.resources = ['PrivacyInfo.xcprivacy']

Expand Down
Loading