├── sample ├── androidApp │ ├── src │ │ └── main │ │ │ ├── assets │ │ │ └── ATScreenNameMapper.json │ │ │ ├── res │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ │ ├── java │ │ │ └── androidApp │ │ │ │ ├── Application.kt │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ └── build.gradle.kts ├── iosApp │ ├── iosApp │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── ATScreenNameMapper.json │ │ ├── AppDelegate.swift │ │ ├── ATUtils │ │ │ ├── ATEventTriggerCompatible+Ext.swift │ │ │ ├── UIControl+AT.swift │ │ │ ├── UITableViewCell+AT.swift │ │ │ ├── UICollectionViewCell+AT.swift │ │ │ ├── UIViewController+Ext.swift │ │ │ └── UIView+DisposeBag.swift │ │ ├── TableViewCellViewController.swift │ │ ├── ControlsViewController.swift │ │ ├── UIIntegrationViewController.swift │ │ ├── GoogleService │ │ │ ├── Dev │ │ │ │ └── GoogleService-Info.plist │ │ │ └── Dist │ │ │ │ └── GoogleService-Info.plist │ │ ├── Info.plist │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── CollectionViewCellViewController.swift │ │ └── Main.storyboard │ ├── iosApp.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcuserdata │ │ │ │ └── user.xcuserdatad │ │ │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── user.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── Podfile │ ├── iosAppTests │ │ ├── Info.plist │ │ └── iosAppTests.swift │ └── iosAppUITests │ │ ├── Info.plist │ │ └── iosAppUITests.swift ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── shared │ ├── src │ │ ├── androidMain │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ │ └── GADelegate.kt │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── EventParam.kt │ │ └── iosX64Main │ │ │ └── kotlin │ │ │ └── GADelegate.kt │ ├── shared.podspec │ └── build.gradle.kts ├── gradle.properties ├── build.gradle.kts ├── settings.gradle.kts ├── gradlew.bat └── gradlew ├── images ├── cover.png ├── sh01.png ├── sh02.png ├── sh03.gif ├── sh04.png ├── kmm_review_sh01.png ├── kmm_review_sh02.png ├── kmm_review_sh03.png └── kmm_review_sh04.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── androidTest │ ├── assets │ │ └── ATScreenNameMapper.json │ └── kotlin │ │ ├── JsonParserTest.kt │ │ └── ATEventTriggerExtTest.kt ├── commonMain │ └── kotlin │ │ └── com │ │ └── linecorp │ │ └── abc │ │ └── analytics │ │ ├── utils │ │ └── JsonParser.kt │ │ ├── interfaces │ │ ├── ATDynamicScreenNameMappable.kt │ │ └── ATEventParamProvider.kt │ │ ├── extensions │ │ ├── IterableExt.kt │ │ ├── StringExt.kt │ │ └── ListExt.kt │ │ ├── objects │ │ ├── KeyValueContainer.kt │ │ ├── BaseParam.kt │ │ └── ATEventObject.kt │ │ ├── Configuration.kt │ │ ├── ATEventDelegate.kt │ │ ├── ATEventCenter.kt │ │ ├── ATScreenNameMapper.kt │ │ └── triggers │ │ └── ATEventTrigger.kt ├── iosMain │ └── kotlin │ │ └── com │ │ └── linecorp │ │ └── abc │ │ └── analytics │ │ ├── triggers │ │ ├── ATEventTriggerCompatible.kt │ │ ├── ATEventTriggerFactory.kt │ │ └── ATEventTriggerExt.kt │ │ ├── objects │ │ └── AnyKeyValueContainer.kt │ │ ├── utils │ │ ├── SwizzleMethodReceiver.kt │ │ ├── JsonParser.kt │ │ ├── SwizzleMethodManager.kt │ │ └── UIViewControllerUtil.kt │ │ ├── extensions │ │ └── UIViewControllerExt.kt │ │ ├── Configuration.kt │ │ ├── ATScreenViewDetector.kt │ │ └── ATEventCenter.kt └── androidMain │ ├── kotlin │ └── com │ │ └── linecorp │ │ └── abc │ │ └── analytics │ │ ├── utils │ │ ├── ModuleInitializer.kt │ │ └── JsonParser.kt │ │ ├── extensions │ │ └── ContextExt.kt │ │ ├── Configuration.kt │ │ ├── observers │ │ ├── ScreenCaptureObserver.kt │ │ └── ActivityLifecycleObserver.kt │ │ ├── triggers │ │ ├── view │ │ │ ├── ViewExt.kt │ │ │ └── ATEventTriggerExt.kt │ │ └── PropertyStorage.kt │ │ └── ATEventCenter.kt │ └── AndroidManifest.xml ├── gradle.properties ├── .gitignore ├── settings.gradle.kts ├── CONTRIBUTING.md ├── kmm_analytics_tools.podspec ├── gradlew.bat ├── gradlew ├── CODE_OF_CONDUCT.md ├── LICENSE └── README.md /sample/androidApp/src/main/assets/ATScreenNameMapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "MainActivity": "main" 3 | } -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/cover.png -------------------------------------------------------------------------------- /images/sh01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/sh01.png -------------------------------------------------------------------------------- /images/sh02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/sh02.png -------------------------------------------------------------------------------- /images/sh03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/sh03.gif -------------------------------------------------------------------------------- /images/sh04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/sh04.png -------------------------------------------------------------------------------- /images/kmm_review_sh01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/kmm_review_sh01.png -------------------------------------------------------------------------------- /images/kmm_review_sh02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/kmm_review_sh02.png -------------------------------------------------------------------------------- /images/kmm_review_sh03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/kmm_review_sh03.png -------------------------------------------------------------------------------- /images/kmm_review_sh04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/images/kmm_review_sh04.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/sample/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /src/androidTest/assets/ATScreenNameMapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "TestViewController": "test", 3 | "MyPageViewController": "mypage", 4 | "ViewController": "root" 5 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/utils/JsonParser.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.utils 2 | 3 | expect class JsonParser() { 4 | fun parse(filename: String): Map? 5 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/interfaces/ATDynamicScreenNameMappable.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.interfaces 2 | 3 | interface ATDynamicScreenNameMappable { 4 | fun mapScreenName() : String? 5 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/iosApp/iosApp.xcodeproj/xcuserdata/user.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /sample/iosApp/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '10.0' 4 | 5 | install! 'cocoapods', :deterministic_uuids => false 6 | 7 | target 'iosApp' do 8 | pod 'shared', :path => '../shared/' 9 | pod 'RxCocoa' 10 | pod 'RxSwift' 11 | end 12 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/extensions/IterableExt.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.extensions 2 | 3 | inline fun Iterable.contains(predicate: (T) -> Boolean): Boolean { 4 | return filterTo(ArrayList(), predicate).count() > 0 5 | } -------------------------------------------------------------------------------- /sample/androidApp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | -------------------------------------------------------------------------------- /sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/user.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/abc-kmm-analytics-tools/HEAD/sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/user.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ATScreenNameMapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "UIIntegrationViewController": "ui integration", 3 | "ControlsViewController": "controls sample", 4 | "TableViewCellViewController": "tableViewCell sample", 5 | "CollectionViewCellViewController": "collectionViewCell sample" 6 | } 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 08 22:13:34 KST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /sample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 08 22:13:34 KST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /sample/shared/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/iosMain/kotlin/com/linecorp/abc/analytics/triggers/ATEventTriggerCompatible.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.triggers 2 | 3 | interface ATEventTriggerCompatible { 4 | fun registerTrigger(invoke: () -> Unit) 5 | } 6 | 7 | interface ATEventTriggerUICompatible: ATEventTriggerCompatible { 8 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/extensions/StringExt.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.extensions 2 | 3 | fun String.camelToSnakeCase() = fold(StringBuilder(length)) { acc, c -> 4 | if (c in 'A'..'Z') (if (acc.isNotEmpty()) acc.append('_') else acc).append(c + ('a' - 'A')) 5 | else acc.append(c) 6 | }.toString() -------------------------------------------------------------------------------- /sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/objects/KeyValueContainer.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.objects 2 | 3 | import com.linecorp.abc.analytics.extensions.camelToSnakeCase 4 | 5 | interface KeyValueContainer { 6 | val key: String 7 | get() = this::class.simpleName?.camelToSnakeCase()?.lowercase() ?: "" 8 | val value: Any 9 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/objects/BaseParam.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.objects 2 | 3 | sealed class BaseParam: KeyValueContainer { 4 | data class ClickSource(override val value: String): BaseParam() 5 | data class ScreenClass(override val value: String): BaseParam() 6 | data class ScreenName(override val value: String): BaseParam() 7 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | return true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/EventParam.kt: -------------------------------------------------------------------------------- 1 | import com.linecorp.abc.analytics.objects.KeyValueContainer 2 | 3 | enum class ClickSource(val value: String) { 4 | Hello("hello") 5 | } 6 | 7 | sealed class EventParam: KeyValueContainer { 8 | data class UserName(override val value: String): EventParam() 9 | data class ViewTime(override val value: Int): EventParam() 10 | } -------------------------------------------------------------------------------- /src/iosMain/kotlin/com/linecorp/abc/analytics/objects/AnyKeyValueContainer.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.objects 2 | 3 | import com.linecorp.abc.analytics.extensions.camelToSnakeCase 4 | 5 | open class AnyKeyValueContainer(override val value: T) : KeyValueContainer { 6 | override val key: String 7 | get() = this::class.simpleName?.camelToSnakeCase()?.lowercase() ?: "" 8 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle 2 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 3 | 4 | #Kotlin 5 | kotlin.code.style=official 6 | kotlin.mpp.enableGranularSourceSetsMetadata=false 7 | kotlin.native.enableDependencyPropagation=false 8 | kotlin.mpp.stability.nowarn=true 9 | kotlinVersion=1.5.21 10 | 11 | #Android 12 | android.useAndroidX=true 13 | android.enableJetifier=true 14 | agpVersion=7.0.1 -------------------------------------------------------------------------------- /sample/gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle 2 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 3 | 4 | #Kotlin 5 | kotlin.code.style=official 6 | kotlin.mpp.enableGranularSourceSetsMetadata=true 7 | kotlin.native.enableDependencyPropagation=false 8 | kotlin.mpp.stability.nowarn=true 9 | kotlinVersion=1.5.21 10 | 11 | #Android 12 | android.useAndroidX=true 13 | android.enableJetifier=true 14 | agpVersion=7.0.1 -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/interfaces/ATEventParamProvider.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.interfaces 2 | 3 | import com.linecorp.abc.analytics.Event 4 | import com.linecorp.abc.analytics.objects.KeyValueContainer 5 | 6 | typealias ATEventParamProviderFunc = () -> List 7 | 8 | interface ATEventParamProvider { 9 | fun params(event: Event, source: Any?): List 10 | } -------------------------------------------------------------------------------- /sample/androidApp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/extensions/ListExt.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.extensions 2 | 3 | import com.linecorp.abc.analytics.objects.BaseParam 4 | import com.linecorp.abc.analytics.objects.KeyValueContainer 5 | 6 | internal fun List.screenClass() = 7 | firstOrNull { it is BaseParam.ScreenClass } 8 | 9 | internal fun List.screenName() = 10 | firstOrNull { it is BaseParam.ScreenName } -------------------------------------------------------------------------------- /src/iosMain/kotlin/com/linecorp/abc/analytics/utils/SwizzleMethodReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.utils 2 | 3 | import com.linecorp.abc.analytics.ATScreenViewDetector 4 | import platform.UIKit.UIViewController 5 | 6 | @Suppress("UNUSED", "CAST_NEVER_SUCCEEDS") 7 | class SwizzleMethodReceiver { 8 | @Suppress("UNUSED_PARAMETER") 9 | fun viewWillAppear(animated: Boolean) { 10 | ATScreenViewDetector.notify(this as UIViewController) 11 | } 12 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ATUtils/ATEventTriggerCompatible+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATEventTriggerCompatible.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/15. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import UIKit 11 | 12 | extension ATEventTriggerCompatible { 13 | var eventTrigger: ATEventTrigger { 14 | ATEventTriggerFactory.Companion().create(owner: self) as! ATEventTrigger 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/iosApp/iosApp.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | iosApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 14 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/Configuration.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics 2 | 3 | expect class Configuration { 4 | internal var canTrackScreenCaptureBlock: (() -> Boolean)? 5 | internal val delegates: MutableList 6 | } 7 | 8 | fun Configuration.canTrackScreenCapture(block: () -> Boolean) { 9 | canTrackScreenCaptureBlock = block 10 | } 11 | 12 | fun Configuration.register(delegate: ATEventDelegate) { 13 | delegates.add(delegate) 14 | } -------------------------------------------------------------------------------- /sample/shared/src/androidMain/kotlin/GADelegate.kt: -------------------------------------------------------------------------------- 1 | 2 | import android.util.Log 3 | import com.linecorp.abc.analytics.ATEventDelegate 4 | 5 | class GADelegate: ATEventDelegate { 6 | 7 | private val tag = GADelegate::class.java.toString() 8 | 9 | override fun setup() { 10 | } 11 | 12 | override fun setUserProperties() { 13 | } 14 | 15 | override fun send(event: String, params: Map) { 16 | Log.d(tag, "send -> event: $event, params: $params") 17 | } 18 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ATUtils/UIControl+AT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+ATEventTriggerUICompatible.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/15. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import UIKit 11 | 12 | extension UIControl: ATEventTriggerUICompatible { 13 | public func registerTrigger(invoke: @escaping () -> Void) { 14 | rx.controlEvent(.touchUpInside) 15 | .bind { invoke() } 16 | .disposed(by: disposeBag) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle/ 3 | .idea/ 4 | /build 5 | /captures 6 | /gen 7 | sample/.idea/ 8 | sample/.gradle/ 9 | sample/build/ 10 | sample/androidApp/build/ 11 | sample/shared/build/ 12 | sample/shared/build/local.properties 13 | sample/shared/build/gradle 14 | sample/shared/build/gradlew 15 | sample/shared/build/gradlew.bat 16 | sample/iosApp/Pods/ 17 | sample/iosApp/Podfile.lock 18 | sample/iosApp/iosApp.xcworkspace 19 | sample/iosApp/iosApp.xcodeproj/project.pbxproj 20 | .externalNativeBuild 21 | .cxx 22 | .DS_Store 23 | local.properties 24 | -------------------------------------------------------------------------------- /sample/androidApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | } 5 | 6 | dependencies { 7 | implementation(project(":shared")) 8 | implementation("com.google.android.material:material:1.3.0") 9 | implementation("androidx.appcompat:appcompat:1.2.0") 10 | implementation("androidx.constraintlayout:constraintlayout:2.0.4") 11 | } 12 | 13 | android { 14 | compileSdk = 30 15 | defaultConfig { 16 | applicationId = "sample.androidApp" 17 | minSdk = 24 18 | targetSdk = 30 19 | } 20 | } -------------------------------------------------------------------------------- /sample/androidApp/src/main/java/androidApp/Application.kt: -------------------------------------------------------------------------------- 1 | package androidApp 2 | 3 | import GADelegate 4 | import com.linecorp.abc.analytics.ATEventCenter 5 | import com.linecorp.abc.analytics.canTrackScreenCapture 6 | import com.linecorp.abc.analytics.register 7 | 8 | class Application : android.app.Application() { 9 | override fun onCreate() { 10 | super.onCreate() 11 | 12 | ATEventCenter.setConfiguration { 13 | canTrackScreenView { true } 14 | canTrackScreenCapture { true } 15 | register(GADelegate()) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/androidMain/kotlin/com/linecorp/abc/analytics/utils/ModuleInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.utils 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import com.linecorp.abc.analytics.ATEventCenter 6 | 7 | @Suppress("UNUSED") 8 | internal class ModuleInitializer: Initializer { 9 | override fun create(context: Context): Int { 10 | ATEventCenter.configuration.context = context 11 | return 0 12 | } 13 | override fun dependencies(): List>> { 14 | return emptyList() 15 | } 16 | } -------------------------------------------------------------------------------- /sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | mavenLocal() 7 | } 8 | dependencies { 9 | val agpVersion: String by project 10 | val kotlinVersion: String by project 11 | classpath("com.android.tools.build:gradle:$agpVersion") 12 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | mavenLocal() 21 | } 22 | } -------------------------------------------------------------------------------- /sample/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | resolutionStrategy { 8 | eachPlugin { 9 | if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") { 10 | val agpVersion: String by settings 11 | useModule("com.android.tools.build:gradle:$agpVersion") 12 | } 13 | } 14 | } 15 | } 16 | rootProject.name = "sample" 17 | 18 | include(":androidApp") 19 | include(":iosApp") 20 | include(":shared") -------------------------------------------------------------------------------- /src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/iosMain/kotlin/com/linecorp/abc/analytics/extensions/UIViewControllerExt.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.extensions 2 | 3 | import com.linecorp.abc.analytics.ATScreenNameMapper 4 | import com.linecorp.abc.analytics.interfaces.ATDynamicScreenNameMappable 5 | import platform.Foundation.classForCoder 6 | import platform.UIKit.UIViewController 7 | 8 | fun UIViewController.className() = 9 | classForCoder.toString().split(".").last() 10 | 11 | fun UIViewController.screenName(screenClass: String) = 12 | (this as? ATDynamicScreenNameMappable)?.mapScreenName() 13 | ?: ATScreenNameMapper.getScreenName(screenClass) -------------------------------------------------------------------------------- /src/androidMain/kotlin/com/linecorp/abc/analytics/extensions/ContextExt.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.extensions 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | import android.os.Build 6 | import androidx.annotation.RequiresApi 7 | 8 | @RequiresApi(Build.VERSION_CODES.M) 9 | fun Context.topActivityName(): String? { 10 | val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 11 | if (am.appTasks.count() > 0) { 12 | val className = am.appTasks.first().taskInfo.topActivity?.className 13 | return className?.split(".")?.last() 14 | } 15 | return null 16 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/objects/ATEventObject.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.objects 2 | 3 | import com.linecorp.abc.analytics.Event 4 | 5 | internal typealias EventSourceTransform = () -> String? 6 | internal typealias EventParamProvider = (Event, Any?) -> List 7 | 8 | data class ATEventObject( 9 | val event: Event, 10 | val source: String? = null, 11 | val sourceTransform: EventSourceTransform? = null) 12 | { 13 | var provider: EventParamProvider? = null 14 | 15 | fun getEventSource(): String? { 16 | return source ?: sourceTransform?.invoke() 17 | } 18 | } -------------------------------------------------------------------------------- /src/androidMain/kotlin/com/linecorp/abc/analytics/utils/JsonParser.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.utils 2 | 3 | import com.google.gson.Gson 4 | import com.linecorp.abc.analytics.ATEventCenter 5 | 6 | actual class JsonParser { 7 | actual fun parse(filename: String): Map? { 8 | return try { 9 | val string = ATEventCenter.configuration.context.resources.assets 10 | .open(filename) 11 | .bufferedReader() 12 | .use { it.readText() } 13 | Gson().fromJson(string, mutableMapOf().javaClass) 14 | } catch(e: Exception) { 15 | null 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ATUtils/UITableViewCell+AT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewCell+ATEventTriggerCompatible.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/18. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import RxCocoa 11 | import RxSwift 12 | import UIKit 13 | 14 | extension UITableViewCell: ATEventTriggerUICompatible { 15 | public func registerTrigger(invoke: @escaping () -> Void) { 16 | rx.methodInvoked(#selector(setSelected(_:animated:))) 17 | .compactMap { $0.first as? Bool } 18 | .filter { $0 } 19 | .bind { _ in invoke() } 20 | .disposed(by: disposeBag) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/shared/src/iosX64Main/kotlin/GADelegate.kt: -------------------------------------------------------------------------------- 1 | 2 | //import cocoapods.FirebaseAnalytics.FIRAnalytics 3 | import com.linecorp.abc.analytics.ATEventDelegate 4 | import platform.Foundation.NSLog 5 | 6 | class GADelegate: ATEventDelegate { 7 | override fun setup() { 8 | // FIRAnalytics.initialize() 9 | } 10 | 11 | override fun setUserProperties() { 12 | // FIRAnalytics.setUserID("userId") 13 | } 14 | 15 | override fun send(event: String, params: Map) { 16 | val casted = params as Map 17 | NSLog("GADelegate:this:send -> event: $event, params: $casted") 18 | // FIRAnalytics.logEventWithName(event, casted) 19 | } 20 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ATUtils/UICollectionViewCell+AT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewCell+ATEventTriggerCompatible.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/18. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import RxCocoa 11 | import RxSwift 12 | import UIKit 13 | 14 | extension UICollectionViewCell: ATEventTriggerUICompatible { 15 | public func registerTrigger(invoke: @escaping () -> Void) { 16 | rx.methodInvoked(#selector(setter:isSelected)) 17 | .compactMap { $0.first as? Bool } 18 | .filter { $0 } 19 | .bind { _ in invoke() } 20 | .disposed(by: disposeBag) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/ATEventDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics 2 | 3 | import com.linecorp.abc.analytics.objects.KeyValueContainer 4 | 5 | interface ATEventDelegate { 6 | fun mapEventKey(event: Event): String { 7 | return event.value 8 | } 9 | fun mapParamKey(container: KeyValueContainer): String { 10 | return container.key 11 | } 12 | fun send(event: String, params: Map) 13 | fun setup() 14 | fun setUserProperties() 15 | } 16 | 17 | internal fun ATEventDelegate.sendAfterMapping(event: Event, params: List) { 18 | send(mapEventKey(event), params.associate { mapParamKey(it) to it.value.toString() }) 19 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/TableViewCellViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewCellViewController.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/17. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import UIKit 11 | 12 | final class TableViewCellViewController: UITableViewController { 13 | 14 | deinit { 15 | print("deinit:\(self)") 16 | } 17 | } 18 | 19 | final class SampleTableViewCell: UITableViewCell { 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | eventTrigger 24 | .click { [weak textLabel] in textLabel?.text ?? "" } 25 | .register { [EventParam.UserName(value: "steve")] } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/iosMain/kotlin/com/linecorp/abc/analytics/utils/JsonParser.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.utils 2 | 3 | import platform.Foundation.NSBundle 4 | import platform.Foundation.NSData 5 | import platform.Foundation.NSJSONSerialization 6 | import platform.Foundation.dataWithContentsOfFile 7 | 8 | actual class JsonParser { 9 | @Suppress("UNCHECKED_CAST") 10 | actual fun parse(filename: String): Map? { 11 | val split = filename.split(".") 12 | val path = NSBundle.mainBundle.pathForResource(split.first(), split.last()) ?: "" 13 | val data = NSData.dataWithContentsOfFile(path) ?: return null 14 | return NSJSONSerialization.JSONObjectWithData(data, 0, null) as? Map 15 | } 16 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | resolutionStrategy { 8 | eachPlugin { 9 | if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") { 10 | val agpVersion: String by settings 11 | useModule("com.android.tools.build:gradle:$agpVersion") 12 | } 13 | } 14 | } 15 | 16 | val kotlinVersion: String by settings 17 | plugins { 18 | kotlin("multiplatform") version "$kotlinVersion" 19 | kotlin("native.cocoapods") version "$kotlinVersion" 20 | } 21 | } 22 | 23 | rootProject.name = "kmm-analytics-tools" -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/ATEventCenter.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics 2 | 3 | import com.linecorp.abc.analytics.objects.KeyValueContainer 4 | import kotlin.native.concurrent.ThreadLocal 5 | 6 | enum class Event(val value: String) { 7 | CLICK("click"), 8 | EXEC("exec"), 9 | VIEW("view"), 10 | CAPTURE("capture"); 11 | } 12 | 13 | @Suppress("NO_ACTUAL_FOR_EXPECT") 14 | expect class ATEventCenter { 15 | @ThreadLocal 16 | companion object { 17 | var configuration: Configuration 18 | 19 | fun restartDetecting() 20 | fun send(event: Event, params: List) 21 | fun setConfiguration(block: Configuration.() -> Unit) 22 | fun setUserProperties() 23 | } 24 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ATUtils/UIViewController+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Ext.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/17. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import UIKit 11 | 12 | extension UIViewController { 13 | static var topMost: UIViewController? { 14 | UIViewControllerUtil.Companion().topMost() 15 | } 16 | 17 | var className: String { 18 | let regex = try! NSRegularExpression(pattern: "<.(.*)>", options: .allowCommentsAndWhitespace) 19 | let str = String(describing: type(of: self)) 20 | let range = NSMakeRange(0, str.count) 21 | return regex.stringByReplacingMatches(in: str, options: .reportCompletion, range: range, withTemplate: "") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/iosApp/iosAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/iosApp/iosAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/androidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/iosMain/kotlin/com/linecorp/abc/analytics/utils/SwizzleMethodManager.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.utils 2 | 3 | import platform.Foundation.NSClassFromString 4 | import platform.objc.* 5 | 6 | @ThreadLocal 7 | object SwizzleMethodManager { 8 | private var isViewControllerSwizzled = false 9 | 10 | internal fun swizzleUIViewController() { 11 | if (!isViewControllerSwizzled) { 12 | val from = class_getInstanceMethod( 13 | NSClassFromString("UIViewController"), 14 | sel_registerName("viewWillAppear:")) 15 | val to = class_getInstanceMethod( 16 | NSClassFromString("SharedSwizzleMethodReceiver"), 17 | sel_registerName("viewWillAppearAnimated:")) 18 | method_exchangeImplementations(from, to) 19 | isViewControllerSwizzled = true 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample/iosApp/iosAppTests/iosAppTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import iosApp 3 | 4 | class iosAppTests: XCTestCase { 5 | 6 | override func setUp() { 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | } 13 | 14 | func testExample() { 15 | // This is an example of a functional test case. 16 | // Use XCTAssert and related functions to verify your tests produce the correct results. 17 | } 18 | 19 | func testPerformanceExample() { 20 | // This is an example of a performance test case. 21 | self.measure { 22 | // Put the code you want to measure the time of here. 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ControlsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ControlsViewController.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/17. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import UIKit 11 | 12 | final class ControlsViewController: UIViewController { 13 | 14 | deinit { 15 | print("deinit:\(self)") 16 | } 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | control.eventTrigger 22 | .click(source: "hello control") 23 | .register { [EventParam.UserName(value: "steve")] } 24 | button.eventTrigger 25 | .click(source: "hello button") 26 | .register { [EventParam.UserName(value: "steve")] } 27 | } 28 | 29 | @IBOutlet private weak var control: UIControl! 30 | @IBOutlet private weak var button: UIButton! 31 | } 32 | -------------------------------------------------------------------------------- /sample/iosApp/iosApp/ATUtils/UIView+DisposeBag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Ext.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/18. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | import UIKit 11 | 12 | extension UIView { 13 | public var disposeBag: DisposeBag { 14 | get { 15 | guard let value = objc_getAssociatedObject(self, &disposeBagKey) as? DisposeBag else { 16 | let value = DisposeBag() 17 | objc_setAssociatedObject(self, &disposeBagKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 18 | return value 19 | } 20 | return value 21 | } 22 | set { 23 | objc_setAssociatedObject(self, &disposeBagKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 24 | } 25 | } 26 | } 27 | 28 | private var disposeBagKey: UInt8 = 0 29 | -------------------------------------------------------------------------------- /src/androidMain/kotlin/com/linecorp/abc/analytics/Configuration.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import com.linecorp.abc.analytics.extensions.topActivityName 6 | 7 | actual class Configuration { 8 | internal actual var canTrackScreenCaptureBlock: (() -> Boolean)? = null 9 | internal actual val delegates: MutableList = mutableListOf() 10 | 11 | internal var canTrackScreenViewBlock: ((Activity) -> Boolean)? = null 12 | private set 13 | internal var topScreenClassBlock: () -> String? = { context.topActivityName() } 14 | private set 15 | 16 | internal lateinit var context: Context 17 | 18 | fun canTrackScreenView(block: (Activity) -> Boolean) { 19 | canTrackScreenViewBlock = block 20 | } 21 | 22 | fun topScreenClass(block: () -> String?) { 23 | topScreenClassBlock = block 24 | } 25 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/linecorp/abc/analytics/ATScreenNameMapper.kt: -------------------------------------------------------------------------------- 1 | 2 | package com.linecorp.abc.analytics 3 | 4 | import com.linecorp.abc.analytics.utils.JsonParser 5 | 6 | class ATScreenNameMapper { 7 | companion object { 8 | val isConfigured: Boolean 9 | get() { return nameMap != null } 10 | 11 | private const val filename = "ATScreenNameMapper.json" 12 | 13 | private val keyMap: Map? 14 | private val nameMap: Map? 15 | 16 | init { 17 | nameMap = JsonParser().parse(filename) 18 | keyMap = nameMap?.entries?.associateBy({ (_, v) -> v }, { (k, _) -> k }) 19 | } 20 | 21 | fun getClassName(screenName: String): String? { 22 | return keyMap?.get(screenName) 23 | } 24 | 25 | fun getScreenName(key: String): String? { 26 | return nameMap?.get(key) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/androidMain/kotlin/com/linecorp/abc/analytics/observers/ScreenCaptureObserver.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.abc.analytics.observers 2 | 3 | import android.database.ContentObserver 4 | import android.net.Uri 5 | import android.os.Handler 6 | import android.provider.MediaStore 7 | import com.linecorp.abc.analytics.ATEventCenter 8 | import com.linecorp.abc.analytics.Event 9 | 10 | internal class ScreenCaptureObserver(private val handler: Handler): ContentObserver(handler) { 11 | override fun onChange(selfChange: Boolean, uri: Uri?) { 12 | super.onChange(selfChange, uri) 13 | when(uri) { 14 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI -> captureDidDetect() 15 | } 16 | } 17 | 18 | private fun captureDidDetect() { 19 | val canTrackScreenCapture = ATEventCenter.configuration.canTrackScreenCaptureBlock?.invoke() ?: false 20 | if (canTrackScreenCapture) { 21 | ATEventCenter.send(Event.CAPTURE, from = null) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /sample/iosApp/iosApp/UIIntegrationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIIntegrationViewController.swift 3 | // iosApp 4 | // 5 | // Created by Steve Kim on 2021/03/17. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import shared 10 | import UIKit 11 | 12 | final class UIIntegrationViewController: UITableViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | clearsSelectionOnViewWillAppear = true 18 | 19 | ATEventCenter.Companion().setConfiguration { 20 | $0.canTrackScreenView { _ in 21 | true 22 | } 23 | $0.canTrackScreenCapture { 24 | true 25 | } 26 | $0.mapScreenClass { 27 | $0.className 28 | } 29 | $0.topViewController { 30 | UIViewController.topMost 31 | } 32 | $0.register(delegate: GADelegate()) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/androidApp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 170 | 171 | 172 | 173 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | --------------------------------------------------------------------------------