Skip to content
Open
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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ dependencies {

implementation project(':attributed-metrics-api')
implementation project(':attributed-metrics-impl')
internalImplementation project(':attributed-metrics-internal')

implementation project(':breakage-reporting-impl')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2025 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.app.statistics

import android.content.Context
import android.view.View
import com.duckduckgo.app.attributed.metrics.internal.ui.AttributedMetricsSettingPlugin
import com.duckduckgo.di.scopes.ActivityScope
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject

@ContributesMultibinding(ActivityScope::class)
class StatisticsAttributedMetricsPlugin @Inject constructor() : AttributedMetricsSettingPlugin {

override fun getView(context: Context): View {
return StatisticsInternalInfoView(context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2025 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.app.statistics

import android.content.Context
import android.util.AttributeSet
import android.widget.LinearLayout
import android.widget.Toast
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.app.browser.databinding.ViewStatisticsAttributedMetricsBinding
import com.duckduckgo.app.global.install.AppInstallStore
import com.duckduckgo.app.referral.AppReferrerDataStore
import com.duckduckgo.app.statistics.store.StatisticsDataStore
import com.duckduckgo.common.ui.viewbinding.viewBinding
import com.duckduckgo.di.scopes.ViewScope
import dagger.android.support.AndroidSupportInjection
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject

@InjectWith(ViewScope::class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna guess these 2 files and the layout are here rather than in the attributed internal module because some dependencies are within the app module?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, there's some stored data that belongs to other modules and our logic depends on those values. So allowing other modules to include internal views here. I was about to create an statistics-internal setting, but some data also belongs to :app module, and it was too much to create several new settings.

class StatisticsInternalInfoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
) : LinearLayout(context, attrs, defStyle) {

@Inject lateinit var store: StatisticsDataStore

@Inject lateinit var referrerDataStore: AppReferrerDataStore

@Inject lateinit var appInstallStore: AppInstallStore

private val binding: ViewStatisticsAttributedMetricsBinding by viewBinding()

private val dateETFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply {
timeZone = TimeZone.getTimeZone("US/Eastern")
}
override fun onAttachedToWindow() {
AndroidSupportInjection.inject(this)
super.onAttachedToWindow()

binding.searchAtb.apply {
text = store.searchRetentionAtb ?: "unknown"
}

binding.searchAtbSave.setOnClickListener {
store.searchRetentionAtb?.let {
store.searchRetentionAtb = binding.searchAtb.text
}
Toast.makeText(this.context, "Search Atb updated", Toast.LENGTH_SHORT).show()
}

binding.originInput.apply {
text = referrerDataStore.utmOriginAttributeCampaign ?: "unknown"
}

binding.originInputSave.setOnClickListener {
referrerDataStore.utmOriginAttributeCampaign = binding.originInput.text.toString()
Toast.makeText(this.context, "Origin updated", Toast.LENGTH_SHORT).show()
}

binding.installDateInput.apply {
val timestamp = appInstallStore.installTimestamp
text = dateETFormat.format(Date(timestamp))
}

binding.installDateSave.setOnClickListener {
try {
val date = dateETFormat.parse(binding.installDateInput.text)
if (date != null) {
appInstallStore.installTimestamp = date.time
Toast.makeText(this.context, "Install date updated", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this.context, "Invalid date format. Use yyyy-MM-dd", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this.context, "Invalid date format. Use yyyy-MM-dd", Toast.LENGTH_SHORT).show()
}
}
}
}
71 changes: 71 additions & 0 deletions app/src/internal/res/layout/view_statistics_attributed_metrics.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2025 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.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<com.duckduckgo.common.ui.view.text.DaxTextInput
android:id="@+id/searchAtb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:hint="Search Atb"
app:endIcon="@drawable/ic_copy_24" />

<com.duckduckgo.common.ui.view.button.DaxButtonPrimary
android:id="@+id/searchAtbSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_gravity="end"
android:text="Save" />

<com.duckduckgo.common.ui.view.text.DaxTextInput
android:id="@+id/originInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:hint="Origin"
app:endIcon="@drawable/ic_copy_24" />

<com.duckduckgo.common.ui.view.button.DaxButtonPrimary
android:id="@+id/originInputSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_gravity="end"
android:text="Save" />

<com.duckduckgo.common.ui.view.text.DaxTextInput
android:id="@+id/installDateInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:hint="Installation Date (ET time)"
app:endIcon="@drawable/ic_copy_24" />

<com.duckduckgo.common.ui.view.button.DaxButtonPrimary
android:id="@+id/installDateSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_gravity="end"
android:text="Save" />
</LinearLayout>
41 changes: 41 additions & 0 deletions attributed-metrics/attributed-metrics-internal/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'com.squareup.anvil'
id 'com.google.devtools.ksp'
}

apply from: "$rootProject.projectDir/gradle/android-library.gradle"

android {
anvil {
generateDaggerFactories = true // default is false
}
namespace 'com.duckduckgo.attributed.metrics.internal'
testOptions {
unitTests {
includeAndroidResources = true
}
}
}

dependencies {
anvil project(':anvil-compiler')
implementation project(':anvil-annotations')
implementation project(':attributed-metrics-api')
implementation project(':attributed-metrics-impl')
implementation project(':di')
implementation project(':internal-features-api')
implementation project(':navigation-api')
implementation project(':common-utils')
implementation project(':design-system')

implementation AndroidX.appCompat
implementation AndroidX.constraintLayout
implementation Google.android.material
implementation Google.dagger

testImplementation project(path: ':common-test')
testImplementation "org.mockito.kotlin:mockito-kotlin:_"
testImplementation Testing.junit4
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.8.2" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.3)" variant="all" version="8.8.2">

</issues>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity
android:name="com.duckduckgo.app.attributed.metrics.internal.ui.AttributedMetricsDevSettingsActivity"
android:label="Attributed Metrics Dev Settings"
android:exported="false" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.app.attributed.metrics.internal

import android.content.Context
import com.duckduckgo.anvil.annotations.PriorityKey
import com.duckduckgo.app.attributed.metrics.internal.ui.MainAttributedMetricsSettings
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.internal.features.api.InternalFeaturePlugin
import com.duckduckgo.navigation.api.GlobalActivityStarter
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject

@ContributesMultibinding(AppScope::class)
@PriorityKey(InternalFeaturePlugin.ATTRIBUTED_METRICS_SETTINGS_PRIO_KEY)
class AttributedMetricsDevSettingsFeatures @Inject constructor(
private val globalActivityStarter: GlobalActivityStarter,
) : InternalFeaturePlugin {
override fun internalFeatureTitle(): String {
return "Attributed Metrics Settings"
}

override fun internalFeatureSubtitle(): String {
return "Attributed Metrics Dev Settings for internal users"
}

override fun onInternalFeatureClicked(activityContext: Context) {
globalActivityStarter.start(activityContext, MainAttributedMetricsSettings)
}
}
Loading
Loading