diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 65c64e538..d90b3f13e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ android:screenOrientation="sensor" android:supportsRtl="true" android:theme="@style/Theme.App.Starting" + tools:replace="android:allowBackup,android:dataExtractionRules,android:fullBackupContent" tools:targetApi="tiramisu"> + + + + + + + + + + + + + + + + + + + + + + () private val downloadDialogManager by inject() private val calendarSyncScheduler by inject() + private val appsFlyerDeepLinkHandler by inject() private val branchLogger = Logger(BRANCH_TAG) @@ -114,6 +116,7 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder { setupInitialFragment(savedInstanceState) observeLogoutEvent() observeDownloadFailedDialog() + observeAppsFlyerDeepLink() calendarSyncScheduler.scheduleDailySync() } @@ -208,6 +211,14 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder { } } + private fun observeAppsFlyerDeepLink() { + lifecycleScope.launch { + appsFlyerDeepLinkHandler.deepLink.collect { deepLink -> + viewModel.makeExternalRoute(supportFragmentManager, deepLink) + } + } + } + override fun onStart() { super.onStart() diff --git a/app/src/main/java/org/openedx/app/OpenEdXApp.kt b/app/src/main/java/org/openedx/app/OpenEdXApp.kt index 6524cde5d..ad0599e2f 100644 --- a/app/src/main/java/org/openedx/app/OpenEdXApp.kt +++ b/app/src/main/java/org/openedx/app/OpenEdXApp.kt @@ -9,6 +9,7 @@ import io.branch.referral.Branch import org.koin.android.ext.android.inject import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin +import org.openedx.app.deeplink.AppsFlyerDeepLinkHandler import org.openedx.app.deeplink.BranchBrazeDeeplinkHandler import org.openedx.app.di.appModule import org.openedx.app.di.networkingModule @@ -20,6 +21,7 @@ class OpenEdXApp : Application() { private val config by inject() private val pluginManager by inject() + private val appsFlyerDeepLinkHandler by inject() override fun onCreate() { super.onCreate() @@ -44,6 +46,10 @@ class OpenEdXApp : Application() { Branch.getAutoInstance(this) } + if (config.getAppsFlyerConfig().enabled) { + appsFlyerDeepLinkHandler.init(this, config.getAppsFlyerConfig().devKey) + } + if (config.getBrazeConfig().isEnabled && config.getFirebaseConfig().enabled) { val isCloudMessagingEnabled = config.getFirebaseConfig().isCloudMessagingEnabled && config.getBrazeConfig().isPushNotificationsEnabled diff --git a/app/src/main/java/org/openedx/app/deeplink/AppsFlyerDeepLinkHandler.kt b/app/src/main/java/org/openedx/app/deeplink/AppsFlyerDeepLinkHandler.kt new file mode 100644 index 000000000..0a369ee6d --- /dev/null +++ b/app/src/main/java/org/openedx/app/deeplink/AppsFlyerDeepLinkHandler.kt @@ -0,0 +1,60 @@ +package org.openedx.app.deeplink + +import android.content.Context +import com.appsflyer.AppsFlyerLib +import com.appsflyer.deeplink.DeepLinkListener +import com.appsflyer.deeplink.DeepLinkResult +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import org.json.JSONObject +import org.openedx.core.utils.Logger + +class AppsFlyerDeepLinkHandler { + + private val logger = Logger(TAG) + + private val _deepLink = MutableSharedFlow(replay = 1, extraBufferCapacity = 1) + val deepLink: SharedFlow = _deepLink.asSharedFlow() + + fun init(context: Context, devKey: String) { + val appsFlyerLib = AppsFlyerLib.getInstance() + appsFlyerLib.subscribeForDeepLink(object : DeepLinkListener { + override fun onDeepLinking(deepLinkResult: DeepLinkResult) { + when (deepLinkResult.status) { + DeepLinkResult.Status.FOUND -> { + logger.i { "AppsFlyer deep link found." } + _deepLink.tryEmit(DeepLink(buildParams(deepLinkResult))) + } + + DeepLinkResult.Status.NOT_FOUND -> { + logger.i { "AppsFlyer deep link not found." } + } + + DeepLinkResult.Status.ERROR -> { + logger.e { "AppsFlyer deep link resolution failed. Caused by - ${deepLinkResult.error}" } + } + } + } + }) + appsFlyerLib.init(devKey, null, context) + appsFlyerLib.start(context) + } + + // Defaults every OneLink click to the registration screen unless the OneLink + // template explicitly sets a `screen_name` deep link parameter. + private fun buildParams(deepLinkResult: DeepLinkResult): Map { + val clickEvent: JSONObject? = deepLinkResult.deepLink?.clickEvent + val params = mutableMapOf() + clickEvent?.keys()?.forEach { key -> params[key] = clickEvent.optString(key) } + + if (!params.containsKey(DeepLink.Keys.SCREEN_NAME.value)) { + params[DeepLink.Keys.SCREEN_NAME.value] = DeepLinkType.REGISTER.type + } + return params + } + + companion object { + private const val TAG = "AppsFlyer" + } +} diff --git a/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt b/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt index ac494df06..0cff34d3c 100644 --- a/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt +++ b/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt @@ -49,6 +49,7 @@ enum class DeepLinkType(val type: String) { REMOVE_BETA_TESTER("remove_beta_tester"), FORUM_RESPONSE("forum_response"), FORUM_COMMENT("forum_comment"), + REGISTER("register"), NONE(""); companion object { diff --git a/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt b/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt index 2192a6b89..befa6fe83 100644 --- a/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt +++ b/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt @@ -8,6 +8,7 @@ import org.openedx.app.AppRouter import org.openedx.app.MainFragment import org.openedx.app.R import org.openedx.auth.presentation.signin.SignInFragment +import org.openedx.auth.presentation.signup.SignUpFragment import org.openedx.core.FragmentViewType import org.openedx.core.config.Config import org.openedx.core.data.storage.CorePreferences @@ -41,6 +42,7 @@ class DeepLinkRouter( DeepLinkType.DISCOVERY -> navigateToDiscoveryScreen(fm) DeepLinkType.DISCOVERY_COURSE_DETAIL -> navigateToCourseDetail(fm, deepLink) DeepLinkType.DISCOVERY_PROGRAM_DETAIL -> navigateToProgramDetail(fm, deepLink) + DeepLinkType.REGISTER -> navigateToRegistration(fm) else -> handleLoggedOutOrUserNavigation(fm, deepLink) } } @@ -192,6 +194,20 @@ class DeepLinkRouter( } } + private fun navigateToRegistration(fm: FragmentManager) { + when { + isUserLoggedIn -> navigateToDashboard(fm) + !config.isRegistrationEnabled() -> navigateToSignIn(fm) + appRouter.getVisibleFragment(fm = fm) !is SignUpFragment -> { + appRouter.navigateToSignUp( + fm = fm, + courseId = null, + infoType = null + ) + } + } + } + private fun navigateToCourseDashboard( fm: FragmentManager, deepLink: DeepLink, diff --git a/app/src/main/java/org/openedx/app/di/AppModule.kt b/app/src/main/java/org/openedx/app/di/AppModule.kt index cdb240387..36d4a5cbf 100644 --- a/app/src/main/java/org/openedx/app/di/AppModule.kt +++ b/app/src/main/java/org/openedx/app/di/AppModule.kt @@ -16,6 +16,7 @@ import org.openedx.app.AppRouter import org.openedx.app.BuildConfig import org.openedx.app.PluginManager import org.openedx.app.data.storage.PreferencesManager +import org.openedx.app.deeplink.AppsFlyerDeepLinkHandler import org.openedx.app.deeplink.DeepLinkRouter import org.openedx.app.room.AppDatabase import org.openedx.app.room.DATABASE_NAME @@ -129,6 +130,7 @@ val appModule = module { single { get() } single { get() } single { DeepLinkRouter(get(), get(), get(), get(), get(), get()) } + single { AppsFlyerDeepLinkHandler() } single { get() } single { get() } diff --git a/build.gradle b/build.gradle index 674a1057f..ef822abef 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ buildscript { // Third-party library versions branch_sdk_version = '5.20.0' + appsflyer_sdk_version = '6.17.4' play_services_ads_identifier_version = '18.2.0' install_referrer_version = '2.2' snakeyaml_version = '2.4' diff --git a/core/build.gradle b/core/build.gradle index 19be1f57a..9391645c7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -114,6 +114,9 @@ dependencies { api "com.google.android.gms:play-services-ads-identifier:$play_services_ads_identifier_version" api "com.android.installreferrer:installreferrer:$install_referrer_version" + // AppsFlyer SDK Integration + api "com.appsflyer:af-android-sdk:$appsflyer_sdk_version" + // Zip api "net.lingala.zip4j:zip4j:$zip_version" diff --git a/core/src/main/java/org/openedx/core/config/AppsFlyerConfig.kt b/core/src/main/java/org/openedx/core/config/AppsFlyerConfig.kt new file mode 100644 index 000000000..cc3304d96 --- /dev/null +++ b/core/src/main/java/org/openedx/core/config/AppsFlyerConfig.kt @@ -0,0 +1,11 @@ +package org.openedx.core.config + +import com.google.gson.annotations.SerializedName + +data class AppsFlyerConfig( + @SerializedName("ENABLED") + val enabled: Boolean = false, + + @SerializedName("DEV_KEY") + val devKey: String = "", +) diff --git a/core/src/main/java/org/openedx/core/config/Config.kt b/core/src/main/java/org/openedx/core/config/Config.kt index d26741699..1fe858151 100644 --- a/core/src/main/java/org/openedx/core/config/Config.kt +++ b/core/src/main/java/org/openedx/core/config/Config.kt @@ -100,6 +100,10 @@ class Config(context: Context) { return getObjectOrNewInstance(BRANCH, BranchConfig::class.java) } + fun getAppsFlyerConfig(): AppsFlyerConfig { + return getObjectOrNewInstance(APPSFLYER, AppsFlyerConfig::class.java) + } + fun isWhatsNewEnabled(): Boolean { return getBoolean(WHATS_NEW_ENABLED, false) } @@ -189,6 +193,7 @@ class Config(context: Context) { private const val DASHBOARD = "DASHBOARD" private const val EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES" private const val BRANCH = "BRANCH" + private const val APPSFLYER = "APPSFLYER" private const val UI_COMPONENTS = "UI_COMPONENTS" private const val PLATFORM_NAME = "PLATFORM_NAME" } diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml index 952e041de..1ac8e76b3 100644 --- a/default_config/dev/config.yaml +++ b/default_config/dev/config.yaml @@ -64,6 +64,10 @@ BRANCH: HOST: '' ALTERNATE_HOST: '' +APPSFLYER: + ENABLED: false + DEV_KEY: '' + EXPERIMENTAL_FEATURES: APP_LEVEL_DOWNLOADS: ENABLED: false diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml index a7f265a45..75bee4416 100644 --- a/default_config/prod/config.yaml +++ b/default_config/prod/config.yaml @@ -64,6 +64,10 @@ BRANCH: HOST: '' ALTERNATE_HOST: '' +APPSFLYER: + ENABLED: false + DEV_KEY: '' + EXPERIMENTAL_FEATURES: APP_LEVEL_DOWNLOADS: ENABLED: false diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml index a7f265a45..75bee4416 100644 --- a/default_config/stage/config.yaml +++ b/default_config/stage/config.yaml @@ -64,6 +64,10 @@ BRANCH: HOST: '' ALTERNATE_HOST: '' +APPSFLYER: + ENABLED: false + DEV_KEY: '' + EXPERIMENTAL_FEATURES: APP_LEVEL_DOWNLOADS: ENABLED: false