Skip to content

Commit 666b9ab

Browse files
committed
Merge branch 'release/5.215.0' into main
2 parents f90ffbb + 63f0330 commit 666b9ab

File tree

189 files changed

+3649
-2020
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

189 files changed

+3649
-2020
lines changed

.maestro/shared/onboarding.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ appId: com.duckduckgo.mobile.android
55
- tapOn: "let's do it!"
66
- assertVisible:
77
text: ".*Privacy protections activated.*"
8+
- scrollUntilVisible:
9+
element:
10+
text: "choose your browser"
11+
direction: DOWN
812
- tapOn: "choose your browser"
913
- tapOn: "cancel"
1014
- assertVisible:

ad-click/ad-click-impl/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ plugins {
1818
id 'com.android.library'
1919
id 'kotlin-android'
2020
id 'com.squareup.anvil'
21-
id 'com.google.devtools.ksp' version "$ksp_version"
21+
id 'com.google.devtools.ksp'
2222
}
2323

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

anrs/anrs-internal/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ plugins {
1818
id 'com.android.library'
1919
id 'kotlin-android'
2020
id 'com.squareup.anvil'
21-
id 'com.google.devtools.ksp' version "$ksp_version"
21+
id 'com.google.devtools.ksp'
2222
}
2323

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

anrs/anrs-store/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
plugins {
1818
id 'com.android.library'
1919
id 'kotlin-android'
20-
id 'com.google.devtools.ksp' version "$ksp_version"
20+
id 'com.google.devtools.ksp'
2121
}
2222

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

anvil/anvil-compiler/build.gradle

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import static de.fayard.refreshVersions.core.Versions.versionFor
1617

1718
plugins {
1819
id 'java-library'
1920
id 'kotlin'
20-
id 'com.google.devtools.ksp' version "$ksp_version"
21+
id 'com.google.devtools.ksp'
2122
}
2223

2324
apply from: "$rootProject.projectDir/code-formatting.gradle"
@@ -34,8 +35,9 @@ kotlin {
3435
dependencies {
3536
implementation project(path: ':anvil-annotations')
3637

37-
api "com.squareup.anvil:compiler-api:${anvil_version}"
38-
implementation("com.squareup.anvil:compiler-utils:${anvil_version}")
38+
def anvil_version = versionFor(project, "plugin.com.squareup.anvil")
39+
api "com.squareup.anvil:compiler-api:$anvil_version"
40+
implementation("com.squareup.anvil:compiler-utils:$anvil_version")
3941
implementation("com.squareup:kotlinpoet:1.11.0")
4042
implementation Google.dagger
4143
implementation project(":feature-toggles-api")

anvil/anvil-compiler/src/main/java/com/duckduckgo/anvil/compiler/ContributesRemoteFeatureCodeGenerator.kt

+91-19
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ import com.squareup.anvil.compiler.internal.asClassName
2828
import com.squareup.anvil.compiler.internal.buildFile
2929
import com.squareup.anvil.compiler.internal.fqName
3030
import com.squareup.anvil.compiler.internal.reference.*
31+
import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi
3132
import com.squareup.kotlinpoet.*
3233
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
3334
import dagger.BindsOptionalOf
3435
import dagger.Provides
36+
import dagger.multibindings.IntoSet
3537
import java.io.File
3638
import javax.inject.Inject
3739
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
@@ -169,6 +171,36 @@ class ContributesRemoteFeatureCodeGenerator : CodeGenerator {
169171
.build(),
170172
)
171173
}
174+
addFunction(
175+
FunSpec.builder("provides${boundType.shortName}Inventory")
176+
.addAnnotation(Provides::class.asClassName())
177+
.addAnnotation(
178+
AnnotationSpec.builder(singleInstanceAnnotationFqName.asClassName(module))
179+
.addMember("scope = %T::class", scope.asClassName())
180+
.build(),
181+
)
182+
.addAnnotation(IntoSet::class.asClassName())
183+
.addParameter("feature", boundType.asClassName())
184+
.addCode(
185+
CodeBlock.of(
186+
"""
187+
return object : FeatureTogglesInventory {
188+
override suspend fun getAll(): List<Toggle> {
189+
return feature.javaClass.declaredMethods.mapNotNull { method ->
190+
if (method.genericReturnType.toString().contains(Toggle::class.java.canonicalName!!)) {
191+
method.invoke(feature) as Toggle
192+
} else {
193+
null
194+
}
195+
}
196+
}
197+
}
198+
""".trimIndent(),
199+
),
200+
)
201+
.returns(FeatureTogglesInventory::class.asClassName())
202+
.build(),
203+
)
172204
}
173205
.build(),
174206
)
@@ -871,6 +903,31 @@ class ContributesRemoteFeatureCodeGenerator : CodeGenerator {
871903
vmClass: ClassReference.Psi,
872904
module: ModuleDescriptor,
873905
): CustomStorePresence {
906+
fun requireFeatureAndStoreCrossReference(vmClass: Psi, storeClass: ClassReference) {
907+
// check if the store is annotated with RemoteFeatureStoreNamed
908+
if (storeClass.annotations.none { it.fqName == RemoteFeatureStoreNamed::class.fqName }) {
909+
throw AnvilCompilationException(
910+
"${storeClass.fqName} shall be annotated with [RemoteFeatureStoreNamed]",
911+
element = vmClass.clazz.identifyingElement,
912+
)
913+
} else {
914+
// lastly, check that both the feature and store reference each other
915+
val storedDefineFeature = storeClass.annotations
916+
.first { it.fqName == RemoteFeatureStoreNamed::class.fqName }
917+
.remoteFeatureStoreValueOrNull()
918+
919+
// check the boundType to ensure triggers work as expected
920+
val annotation = vmClass.annotations.first { it.fqName == ContributesRemoteFeature::class.fqName }
921+
val featureClass = annotation.boundTypeOrNull() ?: vmClass
922+
if (storedDefineFeature?.fqName != featureClass.fqName) {
923+
throw AnvilCompilationException(
924+
"${vmClass.fqName} and ${featureClass.fqName} don't reference each other",
925+
element = vmClass.clazz.identifyingElement,
926+
)
927+
}
928+
}
929+
}
930+
874931
var exceptionStore = false
875932
var settingsStore = false
876933
var toggleStore = false
@@ -912,33 +969,44 @@ class ContributesRemoteFeatureCodeGenerator : CodeGenerator {
912969
}
913970
with(annotation.settingsStoreOrNull()) {
914971
settingsStore = this != null
915-
if (this != null && this.directSuperTypeReferences()
916-
.none { it.asClassReferenceOrNull()?.fqName == FeatureSettings.Store::class.fqName }
917-
) {
918-
throw AnvilCompilationException(
919-
"${vmClass.fqName} [settingsStore] must extend [FeatureSettings.Store]",
920-
element = vmClass.clazz.identifyingElement,
921-
)
972+
if (this != null) {
973+
// check that the Store is actually a [FeatureSettings.Store]
974+
if (this.directSuperTypeReferences()
975+
.none { it.asClassReferenceOrNull()?.fqName == FeatureSettings.Store::class.fqName }
976+
) {
977+
throw AnvilCompilationException(
978+
"${vmClass.fqName} [settingsStore] must extend [FeatureSettings.Store]",
979+
element = vmClass.clazz.identifyingElement,
980+
)
981+
}
982+
983+
requireFeatureAndStoreCrossReference(vmClass, this)
922984
}
923985
}
924986
with(annotation.exceptionsStoreOrNull()) {
925987
exceptionStore = this != null
926-
if (this != null && this.directSuperTypeReferences()
927-
.none { it.asClassReferenceOrNull()?.fqName == FeatureExceptions.Store::class.fqName }
928-
) {
929-
throw AnvilCompilationException(
930-
"${vmClass.fqName} [exceptionsStore] must extend [FeatureExceptions.Store]",
931-
element = vmClass.clazz.identifyingElement,
932-
)
988+
if (this != null) {
989+
if (this.directSuperTypeReferences()
990+
.none { it.asClassReferenceOrNull()?.fqName == FeatureExceptions.Store::class.fqName }
991+
) {
992+
throw AnvilCompilationException(
993+
"${vmClass.fqName} [exceptionsStore] must extend [FeatureExceptions.Store]",
994+
element = vmClass.clazz.identifyingElement,
995+
)
996+
}
997+
requireFeatureAndStoreCrossReference(vmClass, this)
933998
}
934999
}
9351000
with(annotation.toggleStoreOrNull()) {
9361001
toggleStore = this != null
937-
if (this != null && this.directSuperTypeReferences().none { it.asClassReferenceOrNull()?.fqName == Toggle.Store::class.fqName }) {
938-
throw AnvilCompilationException(
939-
"${vmClass.fqName} [toggleStore] must extend [Toggle.Store]",
940-
element = vmClass.clazz.identifyingElement,
941-
)
1002+
if (this != null) {
1003+
if (this.directSuperTypeReferences().none { it.asClassReferenceOrNull()?.fqName == Toggle.Store::class.fqName }) {
1004+
throw AnvilCompilationException(
1005+
"${vmClass.fqName} [toggleStore] must extend [Toggle.Store]",
1006+
element = vmClass.clazz.identifyingElement,
1007+
)
1008+
}
1009+
requireFeatureAndStoreCrossReference(vmClass, this)
9421010
}
9431011
}
9441012

@@ -993,6 +1061,10 @@ class ContributesRemoteFeatureCodeGenerator : CodeGenerator {
9931061
)
9941062
}
9951063

1064+
private fun AnnotationReference.remoteFeatureStoreValueOrNull(): ClassReference? {
1065+
return argumentAt("value", 0)?.value()
1066+
}
1067+
9961068
private fun AnnotationReference.featureNameOrNull(): String? {
9971069
return argumentAt("featureName", 2)?.value()
9981070
}

app-store/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
plugins {
1818
id 'com.android.library'
1919
id 'kotlin-android'
20-
id 'com.google.devtools.ksp' version "$ksp_version"
20+
id 'com.google.devtools.ksp'
2121
}
2222

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

app-tracking-protection/vpn-impl/lint-baseline.xml

+44
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,50 @@
7474
column="9"/>
7575
</issue>
7676

77+
<issue
78+
id="DenyListedApi"
79+
message="If you find yourself using this API in production, you&apos;re doing something wrong!!"
80+
errorLine1=" setting.self().setEnabled(State(enable = true))"
81+
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
82+
<location
83+
file="src/test/java/com/duckduckgo/mobile/android/vpn/ui/newtab/AppTrackingProtectionNewTabSettingsViewModelTest.kt"
84+
line="55"
85+
column="9"/>
86+
</issue>
87+
88+
<issue
89+
id="DenyListedApi"
90+
message="If you find yourself using this API in production, you&apos;re doing something wrong!!"
91+
errorLine1=" setting.self().setEnabled(State(enable = false))"
92+
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
93+
<location
94+
file="src/test/java/com/duckduckgo/mobile/android/vpn/ui/newtab/AppTrackingProtectionNewTabSettingsViewModelTest.kt"
95+
line="66"
96+
column="9"/>
97+
</issue>
98+
99+
<issue
100+
id="DenyListedApi"
101+
message="If you find yourself using this API in production, you&apos;re doing something wrong!!"
102+
errorLine1=" setting.self().setEnabled(State(enable = false))"
103+
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
104+
<location
105+
file="src/test/java/com/duckduckgo/mobile/android/vpn/ui/newtab/AppTrackingProtectionNewTabSettingsViewModelTest.kt"
106+
line="78"
107+
column="9"/>
108+
</issue>
109+
110+
<issue
111+
id="DenyListedApi"
112+
message="If you find yourself using this API in production, you&apos;re doing something wrong!!"
113+
errorLine1=" setting.self().setEnabled(State(enable = false))"
114+
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
115+
<location
116+
file="src/test/java/com/duckduckgo/mobile/android/vpn/ui/newtab/AppTrackingProtectionNewTabSettingsViewModelTest.kt"
117+
line="86"
118+
column="9"/>
119+
</issue>
120+
77121
<issue
78122
id="DenyListedApi"
79123
message="No one remembers what these constants map to. Use the API level integer value directly since it&apos;s self-defining."

app-tracking-protection/vpn-impl/src/test/java/com/duckduckgo/mobile/android/vpn/ui/newtab/AppTrackingProtectionNewTabSettingsViewModelTest.kt

+6-57
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.duckduckgo.mobile.android.vpn.ui.newtab
1919
import androidx.lifecycle.LifecycleOwner
2020
import app.cash.turbine.test
2121
import com.duckduckgo.common.test.CoroutineTestRule
22-
import com.duckduckgo.feature.toggles.api.Toggle
22+
import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory
2323
import com.duckduckgo.feature.toggles.api.Toggle.State
2424
import com.duckduckgo.mobile.android.vpn.pixels.DeviceShieldPixels
2525
import kotlinx.coroutines.test.runTest
@@ -30,15 +30,14 @@ import org.junit.Rule
3030
import org.junit.Test
3131
import org.mockito.Mockito.mock
3232
import org.mockito.kotlin.verify
33-
import org.mockito.kotlin.whenever
3433

3534
class AppTrackingProtectionNewTabSettingsViewModelTest {
3635

3736
@get:Rule
3837
var coroutinesTestRule = CoroutineTestRule()
3938

4039
private lateinit var testee: AppTrackingProtectionNewTabSettingsViewModel
41-
private val setting: NewTabAppTrackingProtectionSectionSetting = mock()
40+
private val setting = FakeFeatureToggleFactory.create(NewTabAppTrackingProtectionSectionSetting::class.java)
4241
private val lifecycleOwner: LifecycleOwner = mock()
4342
private val pixels: DeviceShieldPixels = mock()
4443

@@ -53,20 +52,7 @@ class AppTrackingProtectionNewTabSettingsViewModelTest {
5352

5453
@Test
5554
fun whenViewCreatedAndSettingEnabledThenViewStateUpdated() = runTest {
56-
whenever(setting.self()).thenReturn(
57-
object : Toggle {
58-
override fun isEnabled(): Boolean {
59-
return true
60-
}
61-
62-
override fun setEnabled(state: State) {
63-
}
64-
65-
override fun getRawStoredState(): State {
66-
return State()
67-
}
68-
},
69-
)
55+
setting.self().setEnabled(State(enable = true))
7056
testee.onCreate(lifecycleOwner)
7157
testee.viewState.test {
7258
expectMostRecentItem().also {
@@ -77,20 +63,8 @@ class AppTrackingProtectionNewTabSettingsViewModelTest {
7763

7864
@Test
7965
fun whenViewCreatedAndSettingDisabledThenViewStateUpdated() = runTest {
80-
whenever(setting.self()).thenReturn(
81-
object : Toggle {
82-
override fun isEnabled(): Boolean {
83-
return false
84-
}
66+
setting.self().setEnabled(State(enable = false))
8567

86-
override fun setEnabled(state: State) {
87-
}
88-
89-
override fun getRawStoredState(): State {
90-
return State()
91-
}
92-
},
93-
)
9468
testee.onCreate(lifecycleOwner)
9569
testee.viewState.test {
9670
expectMostRecentItem().also {
@@ -101,40 +75,15 @@ class AppTrackingProtectionNewTabSettingsViewModelTest {
10175

10276
@Test
10377
fun whenSettingEnabledThenPixelFired() = runTest {
104-
whenever(setting.self()).thenReturn(
105-
object : Toggle {
106-
override fun isEnabled(): Boolean {
107-
return false
108-
}
78+
setting.self().setEnabled(State(enable = false))
10979

110-
override fun setEnabled(state: State) {
111-
}
112-
113-
override fun getRawStoredState(): State {
114-
return State()
115-
}
116-
},
117-
)
11880
testee.onSettingEnabled(true)
11981
verify(pixels).reportNewTabSectionToggled(true)
12082
}
12183

12284
@Test
12385
fun whenSettingDisabledThenPixelFired() = runTest {
124-
whenever(setting.self()).thenReturn(
125-
object : Toggle {
126-
override fun isEnabled(): Boolean {
127-
return false
128-
}
129-
130-
override fun setEnabled(state: State) {
131-
}
132-
133-
override fun getRawStoredState(): State {
134-
return State()
135-
}
136-
},
137-
)
86+
setting.self().setEnabled(State(enable = false))
13887
testee.onSettingEnabled(false)
13988
verify(pixels).reportNewTabSectionToggled(false)
14089
}

0 commit comments

Comments
 (0)