Skip to content

Commit

Permalink
Create RMF matchers for duckPlayerOnboarded and duckPlayerEnabled
Browse files Browse the repository at this point in the history
  • Loading branch information
CrisBarreiro committed Aug 19, 2024
1 parent dc3c086 commit a795252
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class DuckPlayerJSHelper @Inject constructor(
duckPlayer.setUserPreferences(overlayInteracted, privatePlayerModeObject.keys().next())
}

private fun sendDuckPlayerPixel(data: JSONObject) {
private suspend fun sendDuckPlayerPixel(data: JSONObject) {
val pixelName = data.getString("pixelName")
val paramsMap = data.getJSONObject("params").keys().asSequence().associateWith {
data.getJSONObject("params").getString(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interface DuckPlayer {
* @param pixelName The name of the pixel.
* @param pixelData The data associated with the pixel.
*/
fun sendDuckPlayerPixel(pixelName: String, pixelData: Map<String, String>)
suspend fun sendDuckPlayerPixel(pixelName: String, pixelData: Map<String, String>)

/**
* Retrieves the user preferences.
Expand Down
1 change: 1 addition & 0 deletions duckplayer/duckplayer-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
implementation project(':app-build-config-api')
implementation project(':js-messaging-api')
implementation project(':navigation-api')
implementation project(':remote-messaging-api')
implementation project(':settings-api')
implementation project(':statistics')
api AndroidX.dataStore.preferences
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.datastore.preferences.core.stringSetPreferencesKey
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.duckplayer.impl.SharedPreferencesDuckPlayerDataStore.Keys.DUCK_PLAYER_DISABLED_HELP_PAGE
import com.duckduckgo.duckplayer.impl.SharedPreferencesDuckPlayerDataStore.Keys.DUCK_PLAYER_RC
import com.duckduckgo.duckplayer.impl.SharedPreferencesDuckPlayerDataStore.Keys.DUCK_PLAYER_USER_ONBOARDED
import com.duckduckgo.duckplayer.impl.SharedPreferencesDuckPlayerDataStore.Keys.DUCK_PLAYER_YOUTUBE_PATH
import com.duckduckgo.duckplayer.impl.SharedPreferencesDuckPlayerDataStore.Keys.DUCK_PLAYER_YOUTUBE_REFERRER_HEADERS
import com.duckduckgo.duckplayer.impl.SharedPreferencesDuckPlayerDataStore.Keys.OVERLAY_INTERACTED
Expand Down Expand Up @@ -80,6 +81,10 @@ interface DuckPlayerDataStore {
suspend fun getYouTubeReferrerHeaders(): List<String>

suspend fun storeYouTubeReferrerHeaders(youtubeReferrerHeaders: List<String>)

suspend fun setUserOnboarded()

suspend fun getUserOnboarded(): Boolean
}

@ContributesBinding(AppScope::class)
Expand All @@ -98,6 +103,7 @@ class SharedPreferencesDuckPlayerDataStore @Inject constructor(
val DUCK_PLAYER_YOUTUBE_URL = stringPreferencesKey(name = "DUCK_PLAYER_YOUTUBE_URL")
val DUCK_PLAYER_YOUTUBE_VIDEO_ID_QUERY_PARAMS = stringPreferencesKey(name = "DUCK_PLAYER_YOUTUBE_VIDEO_ID_QUERY_PARAMS")
val DUCK_PLAYER_YOUTUBE_EMBED_URL = stringPreferencesKey(name = "DUCK_PLAYER_YOUTUBE_EMBED_URL")
val DUCK_PLAYER_USER_ONBOARDED = booleanPreferencesKey(name = "DUCK_PLAYER_USER_ONBOARDED")
}

private val overlayInteracted: Flow<Boolean>
Expand Down Expand Up @@ -170,6 +176,13 @@ class SharedPreferencesDuckPlayerDataStore @Inject constructor(
}
.distinctUntilChanged()

private val duckPlayerUserOnboarded: Flow<Boolean>
get() = store.data
.map { prefs ->
prefs[Keys.DUCK_PLAYER_USER_ONBOARDED] ?: false
}
.distinctUntilChanged()

override suspend fun getDuckPlayerRemoteConfigJson(): String {
return duckPlayerRC.first()
}
Expand Down Expand Up @@ -257,4 +270,12 @@ class SharedPreferencesDuckPlayerDataStore @Inject constructor(
override suspend fun getYouTubeWatchPath(): String {
return youtubePath.first()
}

override suspend fun setUserOnboarded() {
store.edit { prefs -> prefs[DUCK_PLAYER_USER_ONBOARDED] = true }
}

override suspend fun getUserOnboarded(): Boolean {
return duckPlayerUserOnboarded.first()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.duckplayer.impl

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled
import com.duckduckgo.remote.messaging.api.AttributeMatcherPlugin
import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute
import com.duckduckgo.remote.messaging.api.JsonToMatchingAttributeMapper
import com.duckduckgo.remote.messaging.api.MatchingAttribute
import com.squareup.anvil.annotations.ContributesMultibinding
import dagger.SingleInstanceIn
import javax.inject.Inject

@ContributesMultibinding(
scope = AppScope::class,
boundType = JsonToMatchingAttributeMapper::class,
)
@ContributesMultibinding(
scope = AppScope::class,
boundType = AttributeMatcherPlugin::class,
)
@SingleInstanceIn(AppScope::class)
class DuckPlayerEnabledRMFMatchingAttribute @Inject constructor(
private val duckPlayer: DuckPlayer,
) : JsonToMatchingAttributeMapper, AttributeMatcherPlugin {
override suspend fun evaluate(matchingAttribute: MatchingAttribute): Boolean? {
val duckPlayerEnabled = duckPlayer.getDuckPlayerState() == ENABLED &&
duckPlayer.getUserPreferences().privatePlayerMode.let { it == AlwaysAsk || it == Enabled }
return when (matchingAttribute) {
is DuckPlayerEnabledMatchingAttribute -> duckPlayerEnabled == matchingAttribute.remoteValue
else -> null
}
}

override fun map(
key: String,
jsonMatchingAttribute: JsonMatchingAttribute,
): MatchingAttribute? {
return when (key) {
DuckPlayerEnabledMatchingAttribute.KEY -> {
jsonMatchingAttribute.value?.let {
DuckPlayerEnabledMatchingAttribute(jsonMatchingAttribute.value as Boolean)
}
}
else -> null
}
}
}

internal data class DuckPlayerEnabledMatchingAttribute(
val remoteValue: Boolean,
) : MatchingAttribute {
companion object {
const val KEY = "duckPlayerEnabled"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ interface DuckPlayerFeatureRepository {
suspend fun getYouTubeWatchPath(): String
suspend fun getYouTubeUrl(): String
suspend fun getYouTubeEmbedUrl(): String
suspend fun isOnboarded(): Boolean
suspend fun setUserOnboarded()
}

@ContributesBinding(AppScope::class)
Expand Down Expand Up @@ -185,4 +187,12 @@ class RealDuckPlayerFeatureRepository @Inject constructor(
override suspend fun getYouTubeEmbedUrl(): String {
return duckPlayerDataStore.getYoutubeEmbedUrl()
}

override suspend fun isOnboarded(): Boolean {
return duckPlayerDataStore.getUserOnboarded()
}

override suspend fun setUserOnboarded() {
duckPlayerDataStore.setUserOnboarded()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.duckplayer.impl

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.remote.messaging.api.AttributeMatcherPlugin
import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute
import com.duckduckgo.remote.messaging.api.JsonToMatchingAttributeMapper
import com.duckduckgo.remote.messaging.api.MatchingAttribute
import com.squareup.anvil.annotations.ContributesMultibinding
import dagger.SingleInstanceIn
import javax.inject.Inject

@ContributesMultibinding(
scope = AppScope::class,
boundType = JsonToMatchingAttributeMapper::class,
)
@ContributesMultibinding(
scope = AppScope::class,
boundType = AttributeMatcherPlugin::class,
)
@SingleInstanceIn(AppScope::class)
class DuckPlayerOnboardedRMFMatchingAttribute @Inject constructor(
private val duckPlayerFeatureRepository: DuckPlayerFeatureRepository,
) : JsonToMatchingAttributeMapper, AttributeMatcherPlugin {
override suspend fun evaluate(matchingAttribute: MatchingAttribute): Boolean? {
return when (matchingAttribute) {
is DuckPlayerOnboardedMatchingAttribute -> duckPlayerFeatureRepository.isOnboarded() == matchingAttribute.remoteValue
else -> null
}
}

override fun map(
key: String,
jsonMatchingAttribute: JsonMatchingAttribute,
): MatchingAttribute? {
return when (key) {
DuckPlayerOnboardedMatchingAttribute.KEY -> {
jsonMatchingAttribute.value?.let {
DuckPlayerOnboardedMatchingAttribute(jsonMatchingAttribute.value as Boolean)
}
}
else -> null
}
}
}

internal data class DuckPlayerOnboardedMatchingAttribute(
val remoteValue: Boolean,
) : MatchingAttribute {
companion object {
const val KEY = "duckPlayerOnboarded"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,22 @@ class RealDuckPlayer @Inject constructor(
}
}

override fun sendDuckPlayerPixel(
override suspend fun sendDuckPlayerPixel(
pixelName: String,
pixelData: Map<String, String>,
) {
when (pixelName) {
"overlay" -> pixel.fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS, parameters = pixelData)
"play.use" -> pixel.fire(DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY, parameters = pixelData)
"play.do_not_use" -> pixel.fire(DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE, parameters = pixelData)
else -> {}
val duckPlayerPixelName = when (pixelName) {
"overlay" -> DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS
"play.use" -> DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY
"play.do_not_use" -> DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE
else -> { null }
}

duckPlayerPixelName?.let {
pixel.fire(duckPlayerPixelName, parameters = pixelData)
if (duckPlayerPixelName == DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS) {
duckPlayerFeatureRepository.setUserOnboarded()
}
}
}

Expand Down

0 comments on commit a795252

Please sign in to comment.