Skip to content

Commit df9a78b

Browse files
authored
Add notifyme to waitlist (#2843)
<!-- Note: This checklist is a reminder of our shared engineering expectations. The items in Bold are required If your PR involves UI changes: 1. Upload screenshots or screencasts that illustrate the changes before / after 2. Add them under the UI changes section (feel free to add more columns if needed) 3. Make sure these changes are tested in API 23 and API 26 If your PR does not involve UI changes, you can remove the **UI changes** section --> Task/Issue URL: https://app.asana.com/0/488551667048375/1203975081655927/f ### Description Adds notify me component to waitlist ### Steps to test this PR _Feature 1_ - [ ] fresh install - [ ] go to settings waitlist - [ ] join the waitlist - [ ] if notifications enabled, notifyme component shouldn't appear. If disabled, ensure it appears. ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)|
1 parent 6ad6d9d commit df9a78b

File tree

13 files changed

+175
-12
lines changed

13 files changed

+175
-12
lines changed

app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import android.widget.CompoundButton.OnCheckedChangeListener
2929
import android.widget.Toast
3030
import androidx.annotation.StringRes
3131
import androidx.core.app.NotificationManagerCompat
32+
import androidx.core.view.isVisible
3233
import androidx.lifecycle.Lifecycle
3334
import androidx.lifecycle.flowWithLifecycle
3435
import androidx.lifecycle.lifecycleScope
@@ -275,12 +276,15 @@ class SettingsActivity : DuckDuckGoActivity() {
275276
}
276277
}
277278

278-
private fun updateWindowsSettings(waitlistState: WindowsWaitlistState) {
279+
private fun updateWindowsSettings(waitlistState: WindowsWaitlistState?) {
280+
viewsMore.windowsSetting.isVisible = waitlistState != null
281+
279282
with(viewsMore) {
280283
when (waitlistState) {
281284
is InBeta -> windowsSetting.setSecondaryText(getString(R.string.windows_settings_description_ready))
282285
is JoinedWaitlist -> windowsSetting.setSecondaryText(getString(R.string.windows_settings_description_list))
283286
is NotJoinedQueue -> windowsSetting.setSecondaryText(getString(R.string.windows_settings_description))
287+
null -> {}
284288
}
285289
}
286290
}

app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import com.duckduckgo.mobile.android.vpn.ui.onboarding.VpnStore
5151
import com.duckduckgo.privacy.config.api.Gpc
5252
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
5353
import com.duckduckgo.windows.api.WindowsWaitlist
54+
import com.duckduckgo.windows.api.WindowsWaitlistFeature
5455
import com.duckduckgo.windows.api.WindowsWaitlistState
5556
import javax.inject.Inject
5657
import kotlinx.coroutines.Job
@@ -82,6 +83,7 @@ class SettingsViewModel @Inject constructor(
8283
private val vpnFeaturesRegistry: VpnFeaturesRegistry,
8384
private val autoconsent: Autoconsent,
8485
private val windowsWaitlist: WindowsWaitlist,
86+
private val windowsFeature: WindowsWaitlistFeature,
8587
) : ViewModel(), DefaultLifecycleObserver {
8688

8789
private var deviceShieldStatePollingJob: Job? = null
@@ -104,7 +106,7 @@ class SettingsViewModel @Inject constructor(
104106
val showAutofill: Boolean = false,
105107
val autoconsentEnabled: Boolean = false,
106108
@StringRes val notificationsSettingSubtitleId: Int = R.string.settingsSubtitleNotificationsDisabled,
107-
val windowsWaitlistState: WindowsWaitlistState = WindowsWaitlistState.NotJoinedQueue,
109+
val windowsWaitlistState: WindowsWaitlistState? = null,
108110
)
109111

110112
data class AutomaticallyClearData(
@@ -136,8 +138,8 @@ class SettingsViewModel @Inject constructor(
136138
data class ShowClearWhatDialog(val option: ClearWhatOption) : Command()
137139
data class ShowClearWhenDialog(val option: ClearWhenOption) : Command()
138140
object LaunchMacOs : Command()
139-
object LaunchWindows : Command()
140141
object LaunchNotificationsSettings : Command()
142+
object LaunchWindows : Command()
141143
}
142144

143145
private val viewState = MutableStateFlow(ViewState())
@@ -175,8 +177,8 @@ class SettingsViewModel @Inject constructor(
175177
emailAddress = emailManager.getEmailAddress(),
176178
showAutofill = autofillCapabilityChecker.canAccessCredentialManagementScreen(),
177179
autoconsentEnabled = autoconsent.isSettingEnabled(),
178-
windowsWaitlistState = windowsWaitlist.getWaitlistState(),
179180
notificationsSettingSubtitleId = getNotificationsSettingSubtitleId(notificationsEnabled),
181+
windowsWaitlistState = windowsSettingState(),
180182
),
181183
)
182184
}
@@ -417,6 +419,11 @@ class SettingsViewModel @Inject constructor(
417419
}
418420
}
419421

422+
private fun windowsSettingState(): WindowsWaitlistState? {
423+
if (!windowsFeature.self().isEnabled()) return null
424+
return windowsWaitlist.getWaitlistState()
425+
}
426+
420427
fun onThemeSelected(selectedTheme: DuckDuckGoTheme) {
421428
Timber.d("User toggled theme, theme to set: $selectedTheme")
422429
if (themingDataStore.isCurrentlySelected(selectedTheme)) {

app/src/test/java/com/duckduckgo/app/settings/SettingsViewModelTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import com.duckduckgo.appbuildconfig.api.AppBuildConfig
3939
import com.duckduckgo.autoconsent.api.Autoconsent
4040
import com.duckduckgo.autofill.api.AutofillCapabilityChecker
4141
import com.duckduckgo.feature.toggles.api.FeatureToggle
42+
import com.duckduckgo.feature.toggles.api.Toggle
4243
import com.duckduckgo.mobile.android.ui.DuckDuckGoTheme
4344
import com.duckduckgo.mobile.android.ui.store.ThemingDataStore
4445
import com.duckduckgo.mobile.android.vpn.VpnFeaturesRegistry
@@ -48,6 +49,7 @@ import com.duckduckgo.mobile.android.vpn.ui.onboarding.VpnStore
4849
import com.duckduckgo.privacy.config.api.Gpc
4950
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
5051
import com.duckduckgo.windows.api.WindowsWaitlist
52+
import com.duckduckgo.windows.api.WindowsWaitlistFeature
5153
import com.duckduckgo.windows.api.WindowsWaitlistState
5254
import kotlin.time.ExperimentalTime
5355
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -118,13 +120,19 @@ class SettingsViewModelTest {
118120
@Mock
119121
private lateinit var windowsWaitlist: WindowsWaitlist
120122

123+
@Mock
124+
private lateinit var windowsFeatureToggle: Toggle
125+
121126
@get:Rule
122127
val coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
123128

124129
@Before
125130
fun before() {
126131
MockitoAnnotations.openMocks(this)
127132

133+
val windowsFeature: WindowsWaitlistFeature = mock()
134+
whenever(windowsFeatureToggle.isEnabled()).thenReturn(false)
135+
whenever(windowsFeature.self()).thenReturn(windowsFeatureToggle)
128136
whenever(appTpFeatureConfig.isEnabled(AppTpSetting.OpenBeta)).thenReturn(false)
129137
whenever(mockAppSettingsDataStore.automaticallyClearWhenOption).thenReturn(APP_EXIT_ONLY)
130138
whenever(mockAppSettingsDataStore.automaticallyClearWhatOption).thenReturn(CLEAR_NONE)
@@ -152,6 +160,7 @@ class SettingsViewModelTest {
152160
vpnFeaturesRegistry,
153161
autoconsent,
154162
windowsWaitlist,
163+
windowsFeature,
155164
)
156165

157166
runTest {

macos/macos-impl/src/main/java/com/duckduckgo/macos_impl/MacOsActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import android.content.ActivityNotFoundException
2222
import android.content.Context
2323
import android.content.Intent
2424
import android.os.Bundle
25+
import androidx.core.view.isVisible
2526
import androidx.lifecycle.Lifecycle
2627
import androidx.lifecycle.flowWithLifecycle
2728
import androidx.lifecycle.lifecycleScope
@@ -31,6 +32,7 @@ import com.duckduckgo.di.scopes.ActivityScope
3132
import com.duckduckgo.macos_impl.MacOsViewModel.Command
3233
import com.duckduckgo.macos_impl.MacOsViewModel.Command.GoToWindowsClientSettings
3334
import com.duckduckgo.macos_impl.MacOsViewModel.Command.ShareLink
35+
import com.duckduckgo.macos_impl.MacOsViewModel.ViewState
3436
import com.duckduckgo.macos_impl.databinding.ActivityMacosBinding
3537
import com.duckduckgo.mobile.android.ui.viewbinding.viewBinding
3638
import com.duckduckgo.windows.api.WindowsSettingsNav
@@ -53,6 +55,8 @@ class MacOsActivity : DuckDuckGoActivity() {
5355
override fun onCreate(savedInstanceState: Bundle?) {
5456
super.onCreate(savedInstanceState)
5557

58+
viewModel.viewState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).onEach { render(it) }
59+
.launchIn(lifecycleScope)
5660
viewModel.commands.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).onEach { executeCommand(it) }
5761
.launchIn(lifecycleScope)
5862

@@ -61,6 +65,10 @@ class MacOsActivity : DuckDuckGoActivity() {
6165
configureUiEventHandlers()
6266
}
6367

68+
private fun render(viewState: ViewState) {
69+
binding.lookingForWindowsVersionButton.isVisible = viewState.windowsFeatureEnabled
70+
}
71+
6472
private fun configureUiEventHandlers() {
6573
binding.shareButton.setOnClickListener {
6674
viewModel.onShareClicked()

macos/macos-impl/src/main/java/com/duckduckgo/macos_impl/MacOsViewModel.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,27 @@ import com.duckduckgo.di.scopes.AppScope
2424
import com.duckduckgo.macos_impl.MacOsPixelNames.MACOS_WAITLIST_SHARE_PRESSED
2525
import com.duckduckgo.macos_impl.MacOsViewModel.Command.GoToWindowsClientSettings
2626
import com.duckduckgo.macos_impl.MacOsViewModel.Command.ShareLink
27+
import com.duckduckgo.windows.api.WindowsWaitlistFeature
2728
import javax.inject.Inject
2829
import kotlinx.coroutines.channels.BufferOverflow
2930
import kotlinx.coroutines.channels.Channel
31+
import kotlinx.coroutines.flow.Flow
32+
import kotlinx.coroutines.flow.MutableStateFlow
33+
import kotlinx.coroutines.flow.onStart
3034
import kotlinx.coroutines.flow.receiveAsFlow
3135
import kotlinx.coroutines.launch
3236

3337
@ContributesViewModel(AppScope::class)
3438
class MacOsViewModel @Inject constructor(
3539
private val pixel: Pixel,
40+
private val windowsWaitlistFeature: WindowsWaitlistFeature,
3641
) : ViewModel() {
3742

43+
private val viewStateFlow: MutableStateFlow<ViewState> = MutableStateFlow(ViewState(windowsFeatureEnabled = false))
44+
val viewState: Flow<ViewState> = viewStateFlow.onStart {
45+
updateViewState()
46+
}
47+
3848
private val commandChannel = Channel<Command>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
3949
val commands = commandChannel.receiveAsFlow()
4050

@@ -43,6 +53,8 @@ class MacOsViewModel @Inject constructor(
4353
object GoToWindowsClientSettings : Command()
4454
}
4555

56+
data class ViewState(val windowsFeatureEnabled: Boolean)
57+
4658
fun onShareClicked() {
4759
viewModelScope.launch {
4860
commandChannel.send(ShareLink)
@@ -55,4 +67,12 @@ class MacOsViewModel @Inject constructor(
5567
commandChannel.send(GoToWindowsClientSettings)
5668
}
5769
}
70+
71+
private suspend fun updateViewState() {
72+
viewStateFlow.emit(
73+
viewStateFlow.value.copy(
74+
windowsFeatureEnabled = windowsWaitlistFeature.self().isEnabled(),
75+
),
76+
)
77+
}
5878
}

macos/macos-impl/src/test/java/com/duckduckgo/macos_impl/waitlist/ui/MacOsViewModelTest.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
2020
import app.cash.turbine.test
2121
import com.duckduckgo.app.CoroutineTestRule
2222
import com.duckduckgo.app.statistics.pixels.Pixel
23+
import com.duckduckgo.feature.toggles.api.Toggle
2324
import com.duckduckgo.macos_impl.MacOsPixelNames.MACOS_WAITLIST_SHARE_PRESSED
2425
import com.duckduckgo.macos_impl.MacOsViewModel
2526
import com.duckduckgo.macos_impl.MacOsViewModel.Command.GoToWindowsClientSettings
2627
import com.duckduckgo.macos_impl.MacOsViewModel.Command.ShareLink
28+
import com.duckduckgo.windows.api.WindowsWaitlistFeature
2729
import kotlinx.coroutines.ExperimentalCoroutinesApi
2830
import kotlinx.coroutines.test.runTest
2931
import org.junit.Assert.*
@@ -33,6 +35,7 @@ import org.junit.Test
3335
import org.junit.runner.RunWith
3436
import org.mockito.kotlin.mock
3537
import org.mockito.kotlin.verify
38+
import org.mockito.kotlin.whenever
3639

3740
@ExperimentalCoroutinesApi
3841
@RunWith(AndroidJUnit4::class)
@@ -42,11 +45,12 @@ class MacOsViewModelTest {
4245
var coroutineRule = CoroutineTestRule()
4346

4447
private var mockPixel: Pixel = mock()
48+
private var mockWindowsWaitlistFeature: WindowsWaitlistFeature = mock()
4549
private lateinit var testee: MacOsViewModel
4650

4751
@Before
4852
fun before() {
49-
testee = MacOsViewModel(mockPixel)
53+
testee = MacOsViewModel(mockPixel, mockWindowsWaitlistFeature)
5054
}
5155

5256
@Test
@@ -71,4 +75,15 @@ class MacOsViewModelTest {
7175

7276
verify(mockPixel).fire(MACOS_WAITLIST_SHARE_PRESSED)
7377
}
78+
79+
@Test
80+
fun whenWindowsWaitlistDisabledThenStateHidden() = runTest {
81+
val mockToggle: Toggle = mock()
82+
whenever(mockToggle.isEnabled()).thenReturn(false)
83+
whenever(mockWindowsWaitlistFeature.self()).thenReturn(mockToggle)
84+
85+
testee.viewState.test {
86+
assertFalse(awaitItem().windowsFeatureEnabled)
87+
}
88+
}
7489
}

windows/windows-api/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ android {
3232

3333
dependencies {
3434
implementation Kotlin.stdlib.jdk7
35+
api project(path: ':feature-toggles-api')
3536
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2023 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.windows.api
18+
19+
import com.duckduckgo.feature.toggles.api.Toggle
20+
21+
interface WindowsWaitlistFeature {
22+
@Toggle.DefaultValue(false)
23+
fun self(): Toggle
24+
}

windows/windows-impl/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ dependencies {
3333
implementation project(path: ':browser-api')
3434
implementation project(path: ':statistics')
3535
implementation project(path: ':macos-api')
36+
implementation project(path: ':app-build-config-api')
37+
implementation project(path: ':privacy-config-api')
3638

3739
implementation Kotlin.stdlib.jdk7
3840
implementation AndroidX.appCompat
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) 2023 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.windows.impl
18+
19+
import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
20+
import com.duckduckgo.di.scopes.AppScope
21+
import com.duckduckgo.windows.api.WindowsWaitlistFeature
22+
23+
@ContributesRemoteFeature(
24+
scope = AppScope::class,
25+
boundType = WindowsWaitlistFeature::class,
26+
featureName = "windowsWaitlist",
27+
)
28+
@Suppress("unused")
29+
private interface UnusedWindowsWaitlistFeatureCodegenTrigger

0 commit comments

Comments
 (0)