Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import android.widget.CompoundButton.OnCheckedChangeListener
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.app.NotificationManagerCompat
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
Expand Down Expand Up @@ -275,12 +276,15 @@ class SettingsActivity : DuckDuckGoActivity() {
}
}

private fun updateWindowsSettings(waitlistState: WindowsWaitlistState) {
private fun updateWindowsSettings(waitlistState: WindowsWaitlistState?) {
viewsMore.windowsSetting.isVisible = waitlistState != null

with(viewsMore) {
when (waitlistState) {
is InBeta -> windowsSetting.setSecondaryText(getString(R.string.windows_settings_description_ready))
is JoinedWaitlist -> windowsSetting.setSecondaryText(getString(R.string.windows_settings_description_list))
is NotJoinedQueue -> windowsSetting.setSecondaryText(getString(R.string.windows_settings_description))
null -> {}
}
}
}
Expand Down
13 changes: 10 additions & 3 deletions app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import com.duckduckgo.mobile.android.vpn.ui.onboarding.VpnStore
import com.duckduckgo.privacy.config.api.Gpc
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.windows.api.WindowsWaitlist
import com.duckduckgo.windows.api.WindowsWaitlistFeature
import com.duckduckgo.windows.api.WindowsWaitlistState
import javax.inject.Inject
import kotlinx.coroutines.Job
Expand Down Expand Up @@ -82,6 +83,7 @@ class SettingsViewModel @Inject constructor(
private val vpnFeaturesRegistry: VpnFeaturesRegistry,
private val autoconsent: Autoconsent,
private val windowsWaitlist: WindowsWaitlist,
private val windowsFeature: WindowsWaitlistFeature,
) : ViewModel(), DefaultLifecycleObserver {

private var deviceShieldStatePollingJob: Job? = null
Expand All @@ -104,7 +106,7 @@ class SettingsViewModel @Inject constructor(
val showAutofill: Boolean = false,
val autoconsentEnabled: Boolean = false,
@StringRes val notificationsSettingSubtitleId: Int = R.string.settingsSubtitleNotificationsDisabled,
val windowsWaitlistState: WindowsWaitlistState = WindowsWaitlistState.NotJoinedQueue,
val windowsWaitlistState: WindowsWaitlistState? = null,
)

data class AutomaticallyClearData(
Expand Down Expand Up @@ -136,8 +138,8 @@ class SettingsViewModel @Inject constructor(
data class ShowClearWhatDialog(val option: ClearWhatOption) : Command()
data class ShowClearWhenDialog(val option: ClearWhenOption) : Command()
object LaunchMacOs : Command()
object LaunchWindows : Command()
object LaunchNotificationsSettings : Command()
object LaunchWindows : Command()
}

private val viewState = MutableStateFlow(ViewState())
Expand Down Expand Up @@ -175,8 +177,8 @@ class SettingsViewModel @Inject constructor(
emailAddress = emailManager.getEmailAddress(),
showAutofill = autofillCapabilityChecker.canAccessCredentialManagementScreen(),
autoconsentEnabled = autoconsent.isSettingEnabled(),
windowsWaitlistState = windowsWaitlist.getWaitlistState(),
notificationsSettingSubtitleId = getNotificationsSettingSubtitleId(notificationsEnabled),
windowsWaitlistState = windowsSettingState(),
),
)
}
Expand Down Expand Up @@ -417,6 +419,11 @@ class SettingsViewModel @Inject constructor(
}
}

private fun windowsSettingState(): WindowsWaitlistState? {
if (!windowsFeature.self().isEnabled()) return null
return windowsWaitlist.getWaitlistState()
}

fun onThemeSelected(selectedTheme: DuckDuckGoTheme) {
Timber.d("User toggled theme, theme to set: $selectedTheme")
if (themingDataStore.isCurrentlySelected(selectedTheme)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.autoconsent.api.Autoconsent
import com.duckduckgo.autofill.api.AutofillCapabilityChecker
import com.duckduckgo.feature.toggles.api.FeatureToggle
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.mobile.android.ui.DuckDuckGoTheme
import com.duckduckgo.mobile.android.ui.store.ThemingDataStore
import com.duckduckgo.mobile.android.vpn.VpnFeaturesRegistry
Expand All @@ -48,6 +49,7 @@ import com.duckduckgo.mobile.android.vpn.ui.onboarding.VpnStore
import com.duckduckgo.privacy.config.api.Gpc
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.windows.api.WindowsWaitlist
import com.duckduckgo.windows.api.WindowsWaitlistFeature
import com.duckduckgo.windows.api.WindowsWaitlistState
import kotlin.time.ExperimentalTime
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -118,13 +120,19 @@ class SettingsViewModelTest {
@Mock
private lateinit var windowsWaitlist: WindowsWaitlist

@Mock
private lateinit var windowsFeatureToggle: Toggle

@get:Rule
val coroutineTestRule: CoroutineTestRule = CoroutineTestRule()

@Before
fun before() {
MockitoAnnotations.openMocks(this)

val windowsFeature: WindowsWaitlistFeature = mock()
whenever(windowsFeatureToggle.isEnabled()).thenReturn(false)
whenever(windowsFeature.self()).thenReturn(windowsFeatureToggle)
whenever(appTpFeatureConfig.isEnabled(AppTpSetting.OpenBeta)).thenReturn(false)
whenever(mockAppSettingsDataStore.automaticallyClearWhenOption).thenReturn(APP_EXIT_ONLY)
whenever(mockAppSettingsDataStore.automaticallyClearWhatOption).thenReturn(CLEAR_NONE)
Expand Down Expand Up @@ -152,6 +160,7 @@ class SettingsViewModelTest {
vpnFeaturesRegistry,
autoconsent,
windowsWaitlist,
windowsFeature,
)

runTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
Expand All @@ -31,6 +32,7 @@ import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.macos_impl.MacOsViewModel.Command
import com.duckduckgo.macos_impl.MacOsViewModel.Command.GoToWindowsClientSettings
import com.duckduckgo.macos_impl.MacOsViewModel.Command.ShareLink
import com.duckduckgo.macos_impl.MacOsViewModel.ViewState
import com.duckduckgo.macos_impl.databinding.ActivityMacosBinding
import com.duckduckgo.mobile.android.ui.viewbinding.viewBinding
import com.duckduckgo.windows.api.WindowsSettingsNav
Expand All @@ -53,6 +55,8 @@ class MacOsActivity : DuckDuckGoActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

viewModel.viewState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).onEach { render(it) }
.launchIn(lifecycleScope)
viewModel.commands.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).onEach { executeCommand(it) }
.launchIn(lifecycleScope)

Expand All @@ -61,6 +65,10 @@ class MacOsActivity : DuckDuckGoActivity() {
configureUiEventHandlers()
}

private fun render(viewState: ViewState) {
binding.lookingForWindowsVersionButton.isVisible = viewState.windowsFeatureEnabled
}

private fun configureUiEventHandlers() {
binding.shareButton.setOnClickListener {
viewModel.onShareClicked()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,27 @@ import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.macos_impl.MacOsPixelNames.MACOS_WAITLIST_SHARE_PRESSED
import com.duckduckgo.macos_impl.MacOsViewModel.Command.GoToWindowsClientSettings
import com.duckduckgo.macos_impl.MacOsViewModel.Command.ShareLink
import com.duckduckgo.windows.api.WindowsWaitlistFeature
import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch

@ContributesViewModel(AppScope::class)
class MacOsViewModel @Inject constructor(
private val pixel: Pixel,
private val windowsWaitlistFeature: WindowsWaitlistFeature,
) : ViewModel() {

private val viewStateFlow: MutableStateFlow<ViewState> = MutableStateFlow(ViewState(windowsFeatureEnabled = false))
val viewState: Flow<ViewState> = viewStateFlow.onStart {
updateViewState()
}

private val commandChannel = Channel<Command>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val commands = commandChannel.receiveAsFlow()

Expand All @@ -43,6 +53,8 @@ class MacOsViewModel @Inject constructor(
object GoToWindowsClientSettings : Command()
}

data class ViewState(val windowsFeatureEnabled: Boolean)

fun onShareClicked() {
viewModelScope.launch {
commandChannel.send(ShareLink)
Expand All @@ -55,4 +67,12 @@ class MacOsViewModel @Inject constructor(
commandChannel.send(GoToWindowsClientSettings)
}
}

private suspend fun updateViewState() {
viewStateFlow.emit(
viewStateFlow.value.copy(
windowsFeatureEnabled = windowsWaitlistFeature.self().isEnabled(),
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.turbine.test
import com.duckduckgo.app.CoroutineTestRule
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.macos_impl.MacOsPixelNames.MACOS_WAITLIST_SHARE_PRESSED
import com.duckduckgo.macos_impl.MacOsViewModel
import com.duckduckgo.macos_impl.MacOsViewModel.Command.GoToWindowsClientSettings
import com.duckduckgo.macos_impl.MacOsViewModel.Command.ShareLink
import com.duckduckgo.windows.api.WindowsWaitlistFeature
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert.*
Expand All @@ -33,6 +35,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
Expand All @@ -42,11 +45,12 @@ class MacOsViewModelTest {
var coroutineRule = CoroutineTestRule()

private var mockPixel: Pixel = mock()
private var mockWindowsWaitlistFeature: WindowsWaitlistFeature = mock()
private lateinit var testee: MacOsViewModel

@Before
fun before() {
testee = MacOsViewModel(mockPixel)
testee = MacOsViewModel(mockPixel, mockWindowsWaitlistFeature)
}

@Test
Expand All @@ -71,4 +75,15 @@ class MacOsViewModelTest {

verify(mockPixel).fire(MACOS_WAITLIST_SHARE_PRESSED)
}

@Test
fun whenWindowsWaitlistDisabledThenStateHidden() = runTest {
val mockToggle: Toggle = mock()
whenever(mockToggle.isEnabled()).thenReturn(false)
whenever(mockWindowsWaitlistFeature.self()).thenReturn(mockToggle)

testee.viewState.test {
assertFalse(awaitItem().windowsFeatureEnabled)
}
}
}
1 change: 1 addition & 0 deletions windows/windows-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ android {

dependencies {
implementation Kotlin.stdlib.jdk7
api project(path: ':feature-toggles-api')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 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.windows.api

import com.duckduckgo.feature.toggles.api.Toggle

interface WindowsWaitlistFeature {
@Toggle.DefaultValue(false)
fun self(): Toggle
}
2 changes: 2 additions & 0 deletions windows/windows-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ dependencies {
implementation project(path: ':browser-api')
implementation project(path: ':statistics')
implementation project(path: ':macos-api')
implementation project(path: ':app-build-config-api')
implementation project(path: ':privacy-config-api')

implementation Kotlin.stdlib.jdk7
implementation AndroidX.appCompat
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2023 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.windows.impl

import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.windows.api.WindowsWaitlistFeature

@ContributesRemoteFeature(
scope = AppScope::class,
boundType = WindowsWaitlistFeature::class,
featureName = "windowsWaitlist",
)
@Suppress("unused")
private interface UnusedWindowsWaitlistFeatureCodegenTrigger
Loading