From 0e874e38d4c6b7aa56b672ac0896ca188a1d5e52 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Tue, 19 Nov 2024 18:33:24 +0000 Subject: [PATCH 01/14] duplicate settings screen Note we've also duplicated the xml layout and are now referring to it's binding in the Activity I used a new title of "New Settings" for the activity so it's easy to differentiate and notice if you're using the flagged version. I will remove this once we have the new UI for the items and it's more obvious it's the updated version --- app/src/main/AndroidManifest.xml | 9 + .../app/settings/NewSettingsActivity.kt | 443 ++++++++++++++++++ .../app/settings/NewSettingsViewModel.kt | 303 ++++++++++++ .../main/res/layout/activity_settings_new.xml | 31 ++ .../browser/api/ui/BrowserScreens.kt | 5 + 5 files changed, 791 insertions(+) create mode 100644 app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt create mode 100644 app/src/main/java/com/duckduckgo/app/settings/NewSettingsViewModel.kt create mode 100644 app/src/main/res/layout/activity_settings_new.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 534d53d49c85..01ccb40eef5b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -325,6 +325,15 @@ + + + + + + + @Inject + lateinit var addWidgetLauncher: AddWidgetLauncher + + @Inject + lateinit var appBuildConfig: AppBuildConfig + + @Inject + lateinit var globalActivityStarter: GlobalActivityStarter + + @Inject + lateinit var _proSettingsPlugin: PluginPoint + private val proSettingsPlugin by lazy { + _proSettingsPlugin.getPlugins() + } + + @Inject + lateinit var _duckPlayerSettingsPlugin: PluginPoint + private val duckPlayerSettingsPlugin by lazy { + _duckPlayerSettingsPlugin.getPlugins() + } + + private val viewsPrivacy + get() = binding.includeSettings.contentSettingsPrivacy + + private val viewsSettings + get() = binding.includeSettings.contentSettingsSettings + + private val viewsMore + get() = binding.includeSettings.contentSettingsMore + + private val viewsInternal + get() = binding.includeSettings.contentSettingsInternal + + private val viewsPro + get() = binding.includeSettings.settingsSectionPro + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(binding.root) + setupToolbar(binding.includeToolbar.toolbar) + + configureUiEventHandlers() + configureInternalFeatures() + configureSettings() + lifecycle.addObserver(viewModel) + observeViewModel() + + intent?.getStringExtra(BrowserActivity.LAUNCH_FROM_NOTIFICATION_PIXEL_NAME)?.let { + viewModel.onLaunchedFromNotification(it) + } + } + + private fun configureUiEventHandlers() { + with(viewsPrivacy) { + setAsDefaultBrowserSetting.setClickListener { viewModel.onDefaultBrowserSettingClicked() } + privateSearchSetting.setClickListener { viewModel.onPrivateSearchSettingClicked() } + webTrackingProtectionSetting.setClickListener { viewModel.onWebTrackingProtectionSettingClicked() } + cookiePopupProtectionSetting.setClickListener { viewModel.onCookiePopupProtectionSettingClicked() } + emailSetting.setClickListener { viewModel.onEmailProtectionSettingClicked() } + vpnSetting.setClickListener { viewModel.onAppTPSettingClicked() } + } + + with(viewsSettings) { + homeScreenWidgetSetting.setClickListener { viewModel.userRequestedToAddHomeScreenWidget() } + autofillLoginsSetting.setClickListener { viewModel.onAutofillSettingsClick() } + syncSetting.setClickListener { viewModel.onSyncSettingClicked() } + fireButtonSetting.setClickListener { viewModel.onFireButtonSettingClicked() } + permissionsSetting.setClickListener { viewModel.onPermissionsSettingClicked() } + appearanceSetting.setClickListener { viewModel.onAppearanceSettingClicked() } + accessibilitySetting.setClickListener { viewModel.onAccessibilitySettingClicked() } + aboutSetting.setClickListener { viewModel.onAboutSettingClicked() } + generalSetting.setClickListener { viewModel.onGeneralSettingClicked() } + } + + with(viewsMore) { + macOsSetting.setClickListener { viewModel.onMacOsSettingClicked() } + windowsSetting.setClickListener { viewModel.windowsSettingClicked() } + } + } + + private fun configureSettings() { + if (proSettingsPlugin.isEmpty()) { + viewsPro.gone() + } else { + proSettingsPlugin.forEach { plugin -> + viewsPro.addView(plugin.getView(this)) + } + } + + if (duckPlayerSettingsPlugin.isEmpty()) { + viewsSettings.settingsSectionDuckPlayer.gone() + } else { + duckPlayerSettingsPlugin.forEach { plugin -> + viewsSettings.settingsSectionDuckPlayer.addView(plugin.getView(this)) + } + } + } + + private fun configureInternalFeatures() { + viewsInternal.settingsSectionInternal.visibility = if (internalFeaturePlugins.getPlugins().isEmpty()) View.GONE else View.VISIBLE + internalFeaturePlugins.getPlugins().forEach { feature -> + Timber.v("Adding internal feature ${feature.internalFeatureTitle()}") + val view = TwoLineListItem(this).apply { + setPrimaryText(feature.internalFeatureTitle()) + setSecondaryText(feature.internalFeatureSubtitle()) + } + viewsInternal.settingsInternalFeaturesContainer.addView(view) + view.setClickListener { feature.onInternalFeatureClicked(this) } + } + } + + private fun observeViewModel() { + viewModel.viewState() + .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED) + .distinctUntilChanged() + .onEach { viewState -> + viewState.let { + updateDefaultBrowserViewVisibility(it) + updateDeviceShieldSettings( + it.appTrackingProtectionEnabled, + it.appTrackingProtectionOnboardingShown, + ) + updateEmailSubtitle(it.emailAddress) + updateAutofill(it.showAutofill) + updateSyncSetting(visible = it.showSyncSetting) + updateAutoconsent(it.isAutoconsentEnabled) + updatePrivacyPro(it.isPrivacyProEnabled) + updateDuckPlayer(it.isDuckPlayerEnabled) + } + }.launchIn(lifecycleScope) + + viewModel.commands() + .flowWithLifecycle(lifecycle, Lifecycle.State.CREATED) + .onEach { processCommand(it) } + .launchIn(lifecycleScope) + } + + private fun updatePrivacyPro(isPrivacyProEnabled: Boolean) { + if (isPrivacyProEnabled) { + pixel.fire(PRIVACY_PRO_IS_ENABLED_AND_ELIGIBLE, type = Daily()) + viewsPro.show() + } else { + viewsPro.gone() + } + } + + private fun updateDuckPlayer(isDuckPlayerEnabled: Boolean) { + if (isDuckPlayerEnabled) { + viewsSettings.settingsSectionDuckPlayer.show() + } else { + viewsSettings.settingsSectionDuckPlayer.gone() + } + } + + private fun updateAutofill(autofillEnabled: Boolean) = with(viewsSettings.autofillLoginsSetting) { + visibility = if (autofillEnabled) { + View.VISIBLE + } else { + View.GONE + } + } + + private fun updateEmailSubtitle(emailAddress: String?) { + if (emailAddress.isNullOrEmpty()) { + viewsPrivacy.emailSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.settingsEmailProtectionSubtitle)) + viewsPrivacy.emailSetting.setItemStatus(CheckListItem.CheckItemStatus.DISABLED) + } else { + viewsPrivacy.emailSetting.setSecondaryText(emailAddress) + viewsPrivacy.emailSetting.setItemStatus(CheckListItem.CheckItemStatus.ENABLED) + } + } + + private fun updateSyncSetting(visible: Boolean) { + with(viewsSettings.syncSetting) { + isVisible = visible + } + } + + private fun updateAutoconsent(enabled: Boolean) { + if (enabled) { + viewsPrivacy.cookiePopupProtectionSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.cookiePopupProtectionEnabled)) + viewsPrivacy.cookiePopupProtectionSetting.setItemStatus(CheckListItem.CheckItemStatus.ENABLED) + } else { + viewsPrivacy.cookiePopupProtectionSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.cookiePopupProtectionDescription)) + viewsPrivacy.cookiePopupProtectionSetting.setItemStatus(CheckListItem.CheckItemStatus.DISABLED) + } + } + + private fun processCommand(it: Command?) { + when (it) { + is Command.LaunchDefaultBrowser -> launchDefaultAppScreen() + is Command.LaunchAutofillSettings -> launchAutofillSettings() + is Command.LaunchAccessibilitySettings -> launchAccessibilitySettings() + is Command.LaunchAppTPTrackersScreen -> launchAppTPTrackersScreen() + is Command.LaunchAppTPOnboarding -> launchAppTPOnboardingScreen() + is Command.LaunchEmailProtection -> launchEmailProtectionScreen(it.url) + is Command.LaunchEmailProtectionNotSupported -> launchEmailProtectionNotSupported() + is Command.LaunchAddHomeScreenWidget -> launchAddHomeScreenWidget() + is Command.LaunchMacOs -> launchMacOsScreen() + is Command.LaunchWindows -> launchWindowsScreen() + is Command.LaunchSyncSettings -> launchSyncSettings() + is Command.LaunchPrivateSearchWebPage -> launchPrivateSearchScreen() + is Command.LaunchWebTrackingProtectionScreen -> launchWebTrackingProtectionScreen() + is Command.LaunchCookiePopupProtectionScreen -> launchCookiePopupProtectionScreen() + is Command.LaunchFireButtonScreen -> launchFireButtonScreen() + is Command.LaunchPermissionsScreen -> launchPermissionsScreen() + is Command.LaunchAppearanceScreen -> launchAppearanceScreen() + is Command.LaunchAboutScreen -> launchAboutScreen() + is Command.LaunchGeneralSettingsScreen -> launchGeneralSettingsScreen() + null -> TODO() + } + } + + private fun updateDefaultBrowserViewVisibility(it: SettingsViewModel.ViewState) { + with(viewsPrivacy.setAsDefaultBrowserSetting) { + visibility = if (it.showDefaultBrowserSetting) { + if (it.isAppDefaultBrowser) { + setItemStatus(CheckListItem.CheckItemStatus.ENABLED) + setSecondaryText(getString(com.duckduckgo.app.browser.R.string.settingsDefaultBrowserSetDescription)) + } else { + setItemStatus(CheckListItem.CheckItemStatus.DISABLED) + setSecondaryText(getString(com.duckduckgo.app.browser.R.string.settingsDefaultBrowserNotSetDescription)) + } + View.VISIBLE + } else { + View.GONE + } + } + } + + private fun updateDeviceShieldSettings( + appTPEnabled: Boolean, + appTrackingProtectionOnboardingShown: Boolean, + ) { + with(viewsPrivacy) { + if (appTPEnabled) { + vpnSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.atp_SettingsDeviceShieldEnabled)) + vpnSetting.setItemStatus(CheckListItem.CheckItemStatus.ENABLED) + } else { + if (appTrackingProtectionOnboardingShown) { + vpnSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.atp_SettingsDeviceShieldDisabled)) + vpnSetting.setItemStatus(CheckListItem.CheckItemStatus.WARNING) + } else { + vpnSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.atp_SettingsDeviceShieldNeverEnabled)) + vpnSetting.setItemStatus(CheckListItem.CheckItemStatus.DISABLED) + } + } + } + } + + private fun launchDefaultAppScreen() { + launchDefaultAppActivity() + } + + private fun launchAutofillSettings() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, AutofillSettingsScreen(source = AutofillSettingsLaunchSource.SettingsActivity), options) + } + + private fun launchAccessibilitySettings() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, AccessibilityScreens.Default, options) + } + + private fun launchEmailProtectionScreen(url: String) { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + startActivity(BrowserActivity.intent(this, url, interstitialScreen = true), options) + this.finish() + } + + private fun launchEmailProtectionNotSupported() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, EmailProtectionUnsupportedScreenNoParams, options) + } + + private fun launchMacOsScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, MacOsScreenWithEmptyParams, options) + } + + private fun launchWindowsScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, WindowsScreenWithEmptyParams, options) + } + + private fun launchSyncSettings() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, SyncActivityWithEmptyParams, options) + } + + private fun launchAppTPTrackersScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, AppTrackerActivityWithEmptyParams, options) + } + + private fun launchAppTPOnboardingScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, AppTrackerOnboardingActivityWithEmptyParamsParams, options) + } + + private fun launchAddHomeScreenWidget() { + pixel.fire(AppPixelName.SETTINGS_ADD_HOME_SCREEN_WIDGET_CLICKED) + addWidgetLauncher.launchAddWidget(this) + } + + private fun launchPrivateSearchScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, PrivateSearchScreenNoParams, options) + } + + private fun launchWebTrackingProtectionScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, WebTrackingProtectionScreenNoParams, options) + } + + private fun launchCookiePopupProtectionScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + startActivity(AutoconsentSettingsActivity.intent(this), options) + } + + private fun launchFireButtonScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, FireButtonScreenNoParams, options) + } + + private fun launchPermissionsScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, PermissionsScreenNoParams, options) + } + + private fun launchAppearanceScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, AppearanceScreen.Default, options) + } + + private fun launchAboutScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, AboutScreenNoParams, options) + } + + private fun launchGeneralSettingsScreen() { + val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() + globalActivityStarter.start(this, GeneralSettingsScreenNoParams, options) + } + + companion object { + const val LAUNCH_FROM_NOTIFICATION_PIXEL_NAME = "LAUNCH_FROM_NOTIFICATION_PIXEL_NAME" + + fun intent(context: Context): Intent { + return Intent(context, NewSettingsActivity::class.java) + } + } +} diff --git a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsViewModel.kt b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsViewModel.kt new file mode 100644 index 000000000000..cbe93507e5f1 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsViewModel.kt @@ -0,0 +1,303 @@ +/* + * 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.settings + +import android.annotation.SuppressLint +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.duckduckgo.anvil.annotations.ContributesViewModel +import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserDetector +import com.duckduckgo.app.pixels.AppPixelName.* +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchAboutScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchAccessibilitySettings +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchAddHomeScreenWidget +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchAppTPOnboarding +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchAppTPTrackersScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchAppearanceScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchAutofillSettings +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchCookiePopupProtectionScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchDefaultBrowser +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchEmailProtection +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchEmailProtectionNotSupported +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchFireButtonScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchGeneralSettingsScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchMacOs +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchPermissionsScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchPrivateSearchWebPage +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchSyncSettings +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchWebTrackingProtectionScreen +import com.duckduckgo.app.settings.NewSettingsViewModel.Command.LaunchWindows +import com.duckduckgo.app.statistics.pixels.Pixel +import com.duckduckgo.autoconsent.api.Autoconsent +import com.duckduckgo.autofill.api.AutofillCapabilityChecker +import com.duckduckgo.autofill.api.email.EmailManager +import com.duckduckgo.common.utils.ConflatedJob +import com.duckduckgo.common.utils.DispatcherProvider +import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.duckplayer.api.DuckPlayer +import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.DISABLED_WIH_HELP_LINK +import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED +import com.duckduckgo.mobile.android.app.tracking.AppTrackingProtection +import com.duckduckgo.subscriptions.api.Subscriptions +import com.duckduckgo.sync.api.DeviceSyncState +import javax.inject.Inject +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch + +@SuppressLint("NoLifecycleObserver") +@ContributesViewModel(ActivityScope::class) +class NewSettingsViewModel @Inject constructor( + private val defaultWebBrowserCapability: DefaultBrowserDetector, + private val appTrackingProtection: AppTrackingProtection, + private val pixel: Pixel, + private val emailManager: EmailManager, + private val autofillCapabilityChecker: AutofillCapabilityChecker, + private val deviceSyncState: DeviceSyncState, + private val dispatcherProvider: DispatcherProvider, + private val autoconsent: Autoconsent, + private val subscriptions: Subscriptions, + private val duckPlayer: DuckPlayer, +) : ViewModel(), DefaultLifecycleObserver { + + data class ViewState( + val showDefaultBrowserSetting: Boolean = false, + val isAppDefaultBrowser: Boolean = false, + val appTrackingProtectionOnboardingShown: Boolean = false, + val appTrackingProtectionEnabled: Boolean = false, + val emailAddress: String? = null, + val showAutofill: Boolean = false, + val showSyncSetting: Boolean = false, + val isAutoconsentEnabled: Boolean = false, + val isPrivacyProEnabled: Boolean = false, + val isDuckPlayerEnabled: Boolean = false, + ) + + sealed class Command { + data object LaunchDefaultBrowser : Command() + data class LaunchEmailProtection(val url: String) : Command() + data object LaunchEmailProtectionNotSupported : Command() + data object LaunchAutofillSettings : Command() + data object LaunchAccessibilitySettings : Command() + data object LaunchAddHomeScreenWidget : Command() + data object LaunchAppTPTrackersScreen : Command() + data object LaunchAppTPOnboarding : Command() + data object LaunchMacOs : Command() + data object LaunchWindows : Command() + data object LaunchSyncSettings : Command() + data object LaunchPrivateSearchWebPage : Command() + data object LaunchWebTrackingProtectionScreen : Command() + data object LaunchCookiePopupProtectionScreen : Command() + data object LaunchFireButtonScreen : Command() + data object LaunchPermissionsScreen : Command() + data object LaunchAppearanceScreen : Command() + data object LaunchAboutScreen : Command() + data object LaunchGeneralSettingsScreen : Command() + } + + private val viewState = MutableStateFlow(ViewState()) + + private val command = Channel(1, BufferOverflow.DROP_OLDEST) + private val appTPPollJob = ConflatedJob() + + init { + pixel.fire(SETTINGS_OPENED) + } + + override fun onStart(owner: LifecycleOwner) { + super.onStart(owner) + start() + startPollingAppTPState() + } + + override fun onStop(owner: LifecycleOwner) { + super.onStop(owner) + appTPPollJob.cancel() + } + + @VisibleForTesting + internal fun start() { + val defaultBrowserAlready = defaultWebBrowserCapability.isDefaultBrowser() + + viewModelScope.launch { + viewState.emit( + currentViewState().copy( + isAppDefaultBrowser = defaultBrowserAlready, + showDefaultBrowserSetting = defaultWebBrowserCapability.deviceSupportsDefaultBrowserConfiguration(), + appTrackingProtectionOnboardingShown = appTrackingProtection.isOnboarded(), + appTrackingProtectionEnabled = appTrackingProtection.isRunning(), + emailAddress = emailManager.getEmailAddress(), + showAutofill = autofillCapabilityChecker.canAccessCredentialManagementScreen(), + showSyncSetting = deviceSyncState.isFeatureEnabled(), + isAutoconsentEnabled = autoconsent.isSettingEnabled(), + isPrivacyProEnabled = subscriptions.isEligible(), + isDuckPlayerEnabled = duckPlayer.getDuckPlayerState().let { it == ENABLED || it == DISABLED_WIH_HELP_LINK }, + ), + ) + } + } + + // FIXME + // We need to fix this. This logic as inside the start method but it messes with the unit tests + // because when doing runningBlockingTest {} there is no delay and the tests crashes because this + // becomes a while(true) without any delay + private fun startPollingAppTPState() { + appTPPollJob += viewModelScope.launch(dispatcherProvider.io()) { + while (isActive) { + val isDeviceShieldEnabled = appTrackingProtection.isRunning() + val currentState = currentViewState() + viewState.value = currentState.copy( + appTrackingProtectionOnboardingShown = appTrackingProtection.isOnboarded(), + appTrackingProtectionEnabled = isDeviceShieldEnabled, + isPrivacyProEnabled = subscriptions.isEligible(), + ) + delay(1_000) + } + } + } + + fun viewState(): StateFlow { + return viewState + } + + fun commands(): Flow { + return command.receiveAsFlow() + } + + fun userRequestedToAddHomeScreenWidget() { + viewModelScope.launch { command.send(LaunchAddHomeScreenWidget) } + } + + fun onDefaultBrowserSettingClicked() { + val defaultBrowserSelected = defaultWebBrowserCapability.isDefaultBrowser() + viewModelScope.launch { + viewState.emit(currentViewState().copy(isAppDefaultBrowser = defaultBrowserSelected)) + command.send(LaunchDefaultBrowser) + } + pixel.fire(SETTINGS_DEFAULT_BROWSER_PRESSED) + } + + fun onPrivateSearchSettingClicked() { + viewModelScope.launch { command.send(LaunchPrivateSearchWebPage) } + pixel.fire(SETTINGS_PRIVATE_SEARCH_PRESSED) + } + + fun onWebTrackingProtectionSettingClicked() { + viewModelScope.launch { command.send(LaunchWebTrackingProtectionScreen) } + pixel.fire(SETTINGS_WEB_TRACKING_PROTECTION_PRESSED) + } + + fun onCookiePopupProtectionSettingClicked() { + viewModelScope.launch { command.send(LaunchCookiePopupProtectionScreen) } + pixel.fire(SETTINGS_COOKIE_POPUP_PROTECTION_PRESSED) + } + + fun onAutofillSettingsClick() { + viewModelScope.launch { command.send(LaunchAutofillSettings) } + } + + fun onAccessibilitySettingClicked() { + viewModelScope.launch { command.send(LaunchAccessibilitySettings) } + pixel.fire(SETTINGS_ACCESSIBILITY_PRESSED) + } + + fun onAboutSettingClicked() { + viewModelScope.launch { command.send(LaunchAboutScreen) } + pixel.fire(SETTINGS_ABOUT_PRESSED) + } + + fun onGeneralSettingClicked() { + viewModelScope.launch { command.send(LaunchGeneralSettingsScreen) } + pixel.fire(SETTINGS_GENERAL_PRESSED) + } + + fun onEmailProtectionSettingClicked() { + viewModelScope.launch { + val command = if (emailManager.isEmailFeatureSupported()) { + LaunchEmailProtection(EMAIL_PROTECTION_URL) + } else { + LaunchEmailProtectionNotSupported + } + this@NewSettingsViewModel.command.send(command) + } + pixel.fire(SETTINGS_EMAIL_PROTECTION_PRESSED) + } + + fun onMacOsSettingClicked() { + viewModelScope.launch { command.send(LaunchMacOs) } + pixel.fire(SETTINGS_MAC_APP_PRESSED) + } + + fun windowsSettingClicked() { + viewModelScope.launch { + command.send(LaunchWindows) + } + pixel.fire(SETTINGS_WINDOWS_APP_PRESSED) + } + + fun onAppTPSettingClicked() { + viewModelScope.launch { + if (appTrackingProtection.isOnboarded()) { + command.send(LaunchAppTPTrackersScreen) + } else { + command.send(LaunchAppTPOnboarding) + } + pixel.fire(SETTINGS_APPTP_PRESSED) + } + } + + private fun currentViewState(): ViewState { + return viewState.value + } + + fun onSyncSettingClicked() { + viewModelScope.launch { command.send(LaunchSyncSettings) } + pixel.fire(SETTINGS_SYNC_PRESSED) + } + + fun onFireButtonSettingClicked() { + viewModelScope.launch { command.send(LaunchFireButtonScreen) } + pixel.fire(SETTINGS_FIRE_BUTTON_PRESSED) + } + + fun onPermissionsSettingClicked() { + viewModelScope.launch { command.send(LaunchPermissionsScreen) } + pixel.fire(SETTINGS_PERMISSIONS_PRESSED) + } + + fun onAppearanceSettingClicked() { + viewModelScope.launch { command.send(LaunchAppearanceScreen) } + pixel.fire(SETTINGS_APPEARANCE_PRESSED) + } + + fun onLaunchedFromNotification(pixelName: String) { + pixel.fire(pixelName) + } + + companion object { + const val EMAIL_PROTECTION_URL = "https://duckduckgo.com/email" + } +} diff --git a/app/src/main/res/layout/activity_settings_new.xml b/app/src/main/res/layout/activity_settings_new.xml new file mode 100644 index 000000000000..94b486d9afa3 --- /dev/null +++ b/app/src/main/res/layout/activity_settings_new.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt b/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt index 1d66364fa589..4ed0d6f394dc 100644 --- a/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt +++ b/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt @@ -38,6 +38,11 @@ sealed class BrowserScreens { */ object SettingsScreenNoParams : GlobalActivityStarter.ActivityParams + /** + * Use this model to launch the New Settings screen + */ + object NewSettingsScreenNoParams : GlobalActivityStarter.ActivityParams + /** * Use this model to launch the New Tab Settings screen */ From 27ba691fc78fdd27aa2fe246fc0f7ec8d886acc8 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Tue, 19 Nov 2024 18:36:25 +0000 Subject: [PATCH 02/14] add feature toggle --- .../app/settings/NewSettingsFeature.kt | 31 +++++++++++++++++++ .../app/settings/SettingsActivity.kt | 9 ++++++ 2 files changed, 40 insertions(+) create mode 100644 app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt diff --git a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt new file mode 100644 index 000000000000..88f041a28061 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt @@ -0,0 +1,31 @@ +/* + * 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.settings + +import com.duckduckgo.anvil.annotations.ContributesRemoteFeature +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.feature.toggles.api.Toggle + +@ContributesRemoteFeature( + scope = AppScope::class, + featureName = "newSettings", +) +interface NewSettingsFeature { + + @Toggle.DefaultValue(false) + fun self(): Toggle +} diff --git a/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt index 14e9a1f4f661..d4d35182210e 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt @@ -50,6 +50,7 @@ import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.autoconsent.impl.ui.AutoconsentSettingsActivity import com.duckduckgo.autofill.api.AutofillScreens.AutofillSettingsScreen import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource +import com.duckduckgo.browser.api.ui.BrowserScreens.NewSettingsScreenNoParams import com.duckduckgo.browser.api.ui.BrowserScreens.SettingsScreenNoParams import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.view.gone @@ -108,6 +109,9 @@ class SettingsActivity : DuckDuckGoActivity() { _duckPlayerSettingsPlugin.getPlugins() } + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + private val viewsPrivacy get() = binding.includeSettings.contentSettingsPrivacy @@ -126,6 +130,11 @@ class SettingsActivity : DuckDuckGoActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (newSettingsFeature.self().isEnabled()) { + globalActivityStarter.start(this, NewSettingsScreenNoParams) + finish() + } + setContentView(binding.root) setupToolbar(binding.includeToolbar.toolbar) From 45b4db0370b067e9f700ba40b1dfa25a228d73eb Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Wed, 20 Nov 2024 16:16:47 +0000 Subject: [PATCH 03/14] add notifications to dev settings NotificationsActivity in the next commit --- .../app/dev/settings/DevSettingsActivity.kt | 26 ++++++++++++++----- .../app/dev/settings/DevSettingsViewModel.kt | 5 ++++ .../res/layout/activity_dev_settings.xml | 7 +++++ .../internal/res/values/donottranslate.xml | 2 ++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsActivity.kt b/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsActivity.kt index 9710b8165c34..4a4be1331743 100644 --- a/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsActivity.kt +++ b/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsActivity.kt @@ -34,8 +34,15 @@ import com.duckduckgo.app.browser.R.layout import com.duckduckgo.app.browser.databinding.ActivityDevSettingsBinding import com.duckduckgo.app.browser.webview.WebContentDebuggingFeature import com.duckduckgo.app.dev.settings.DevSettingsViewModel.Command +import com.duckduckgo.app.dev.settings.DevSettingsViewModel.Command.ChangePrivacyConfigUrl +import com.duckduckgo.app.dev.settings.DevSettingsViewModel.Command.CustomTabs +import com.duckduckgo.app.dev.settings.DevSettingsViewModel.Command.Notifications +import com.duckduckgo.app.dev.settings.DevSettingsViewModel.Command.OpenUASelector +import com.duckduckgo.app.dev.settings.DevSettingsViewModel.Command.SendTdsIntent +import com.duckduckgo.app.dev.settings.DevSettingsViewModel.Command.ShowSavedSitesClearedConfirmation import com.duckduckgo.app.dev.settings.customtabs.CustomTabsInternalSettingsActivity import com.duckduckgo.app.dev.settings.db.UAOverride +import com.duckduckgo.app.dev.settings.notifications.NotificationsActivity import com.duckduckgo.app.dev.settings.privacy.TrackerDataDevReceiver.Companion.DOWNLOAD_TDS_INTENT_ACTION import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.menu.PopupMenu @@ -98,6 +105,7 @@ class DevSettingsActivity : DuckDuckGoActivity() { binding.overrideUserAgentSelector.setOnClickListener { viewModel.onUserAgentSelectorClicked() } binding.overridePrivacyRemoteConfigUrl.setOnClickListener { viewModel.onRemotePrivacyUrlClicked() } binding.customTabs.setOnClickListener { viewModel.customTabsClicked() } + binding.notifications.setOnClickListener { viewModel.notificationsClicked() } } private fun observeViewModel() { @@ -119,14 +127,14 @@ class DevSettingsActivity : DuckDuckGoActivity() { .launchIn(lifecycleScope) } - private fun processCommand(it: Command?) { + private fun processCommand(it: Command) { when (it) { - is Command.SendTdsIntent -> sendTdsIntent() - is Command.OpenUASelector -> showUASelector() - is Command.ShowSavedSitesClearedConfirmation -> showSavedSitesClearedConfirmation() - is Command.ChangePrivacyConfigUrl -> showChangePrivacyUrl() - is Command.CustomTabs -> showCustomTabs() - else -> TODO() + is SendTdsIntent -> sendTdsIntent() + is OpenUASelector -> showUASelector() + is ShowSavedSitesClearedConfirmation -> showSavedSitesClearedConfirmation() + is ChangePrivacyConfigUrl -> showChangePrivacyUrl() + is CustomTabs -> showCustomTabs() + Notifications -> showNotifications() } } @@ -167,6 +175,10 @@ class DevSettingsActivity : DuckDuckGoActivity() { startActivity(CustomTabsInternalSettingsActivity.intent(this), options) } + private fun showNotifications() { + startActivity(NotificationsActivity.intent(this)) + } + companion object { fun intent(context: Context): Intent { return Intent(context, DevSettingsActivity::class.java) diff --git a/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsViewModel.kt b/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsViewModel.kt index e4328b2ca6a3..bb08f10b8076 100644 --- a/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsViewModel.kt +++ b/app/src/internal/java/com/duckduckgo/app/dev/settings/DevSettingsViewModel.kt @@ -60,6 +60,7 @@ class DevSettingsViewModel @Inject constructor( object ShowSavedSitesClearedConfirmation : Command() object ChangePrivacyConfigUrl : Command() object CustomTabs : Command() + data object Notifications : Command() } private val viewState = MutableStateFlow(ViewState()) @@ -137,4 +138,8 @@ class DevSettingsViewModel @Inject constructor( command.send(Command.ShowSavedSitesClearedConfirmation) } } + + fun notificationsClicked() { + viewModelScope.launch { command.send(Command.Notifications) } + } } diff --git a/app/src/internal/res/layout/activity_dev_settings.xml b/app/src/internal/res/layout/activity_dev_settings.xml index bc48624014ef..52027f50670b 100644 --- a/app/src/internal/res/layout/activity_dev_settings.xml +++ b/app/src/internal/res/layout/activity_dev_settings.xml @@ -88,6 +88,13 @@ app:secondaryText="@string/devSettingsScreenCustomTabsSubtitle" /> + + Override UserAgent Override Privacy Remote Config URL Custom Tabs + Notifications + Trigger notifications for testing Click here to customize the Privacy Remote Config URL Load a Custom Tab for a specified URL UserAgent From a81420324196413997fe866271c277155b00a586 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Wed, 20 Nov 2024 16:36:40 +0000 Subject: [PATCH 04/14] add notifications screen and scheduled notifications You can see in the viewmodel that I had some issues with the SurveyAvailableNotification 1. The SurveyAvailableNotification needs a notification in the database, otherwise we get a NoElement exception as we call "last()" on the list of surveys when getting them in the intent 2. The intent hits a repo that hits a DAO, that is not a suspend function, so we have to be off the main thread Next we'll add VPN notifications --- app/src/internal/AndroidManifest.xml | 4 + .../notifications/NotificationsActivity.kt | 113 +++++++++++++++++ .../notifications/NotificationsViewModel.kt | 116 ++++++++++++++++++ .../res/layout/activity_notifications.xml | 54 ++++++++ .../internal/res/values/donottranslate.xml | 5 +- 5 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt create mode 100644 app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt create mode 100644 app/src/internal/res/layout/activity_notifications.xml diff --git a/app/src/internal/AndroidManifest.xml b/app/src/internal/AndroidManifest.xml index b89dff89ff6f..82091edb2c82 100644 --- a/app/src/internal/AndroidManifest.xml +++ b/app/src/internal/AndroidManifest.xml @@ -7,6 +7,10 @@ android:name="com.duckduckgo.app.dev.settings.DevSettingsActivity" android:label="@string/devSettingsTitle" android:parentActivityName="com.duckduckgo.app.settings.SettingsActivity" /> + + when (command) { + is TriggerNotification -> addNotification(id = command.notificationItem.id, notification = command.notificationItem.notification) + } + }.launchIn(lifecycleScope) + } + + private fun render(viewState: ViewState) { + viewState.scheduledNotifications.forEach { notificationItem -> + buildNotificationItem( + + title = notificationItem.title, + subtitle = notificationItem.subtitle, + onClick = { viewModel.onNotificationItemClick(notificationItem) }, + ).also { + binding.scheduledNotificationsContainer.addView(it) + } + } + } + + private fun buildNotificationItem( + title: String, + subtitle: String, + onClick: () -> Unit, + ): TwoLineListItem { + return TwoLineListItem(this).apply { + setPrimaryText(title) + setSecondaryText(subtitle) + setOnClickListener { onClick() } + } + } + + private fun addNotification( + id: Int, + notification: Notification + ) { + NotificationManagerCompat.from(this) + .checkPermissionAndNotify(context = this, id = id, notification = notification) + } + + companion object { + + fun intent(context: Context): Intent { + return Intent(context, NotificationsActivity::class.java) + } + } +} diff --git a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt new file mode 100644 index 000000000000..c2c558918a1d --- /dev/null +++ b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt @@ -0,0 +1,116 @@ +/* + * 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.dev.settings.notifications + +import android.app.Notification +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.duckduckgo.anvil.annotations.ContributesViewModel +import com.duckduckgo.app.dev.settings.notifications.NotificationViewModel.ViewState.NotificationItem +import com.duckduckgo.app.notification.NotificationFactory +import com.duckduckgo.app.notification.model.SchedulableNotificationPlugin +import com.duckduckgo.app.survey.api.SurveyRepository +import com.duckduckgo.app.survey.model.Survey +import com.duckduckgo.app.survey.model.Survey.Status.SCHEDULED +import com.duckduckgo.app.survey.notification.SurveyAvailableNotification +import com.duckduckgo.common.utils.DispatcherProvider +import com.duckduckgo.common.utils.plugins.PluginPoint +import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.networkprotection.impl.notification.NetPDisabledNotificationBuilder +import javax.inject.Inject +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@ContributesViewModel(ActivityScope::class) +class NotificationViewModel @Inject constructor( + private val dispatcher: DispatcherProvider, + private val schedulableNotificationPluginPoint: PluginPoint, + private val factory: NotificationFactory, + private val surveyRepository: SurveyRepository, +) : ViewModel() { + + data class ViewState( + val scheduledNotifications: List = emptyList(), + ) { + + data class NotificationItem( + val id: Int, + val title: String, + val subtitle: String, + val notification: Notification + ) + } + + sealed class Command { + data class TriggerNotification(val notificationItem: NotificationItem) : Command() + } + + private val _viewState = MutableStateFlow(ViewState()) + val viewState = _viewState.asStateFlow() + + private val _command = Channel(1, BufferOverflow.DROP_OLDEST) + val command = _command.receiveAsFlow() + + init { + viewModelScope.launch { + val scheduledNotificationItems = schedulableNotificationPluginPoint.getPlugins().map { plugin -> + + // The survey notification will crash if we do not have a survey in the database + if (plugin.getSchedulableNotification().javaClass == SurveyAvailableNotification::class.java) { + withContext(dispatcher.io()) { + addTestSurvey() + } + } + + // the survey intent hits the DB, so we need to do this on IO + val launchIntent = withContext(dispatcher.io()) { plugin.getLaunchIntent() } + + NotificationItem( + id = plugin.getSpecification().systemId, + title = plugin.getSpecification().title, + subtitle = plugin.getSpecification().description, + notification = factory.createNotification(plugin.getSpecification(), launchIntent, null), + ) + } + + _viewState.update { it.copy(scheduledNotifications = scheduledNotificationItems) } + } + } + + private fun addTestSurvey() { + surveyRepository.persistSurvey( + Survey( + "testSurveyId", + "https://youtu.be/dQw4w9WgXcQ?si=iztopgFbXoWUnoOE", + daysInstalled = 1, + status = SCHEDULED, + ), + ) + } + + fun onNotificationItemClick(notificationItem: NotificationItem) { + viewModelScope.launch { + _command.send(Command.TriggerNotification(notificationItem)) + } + } +} diff --git a/app/src/internal/res/layout/activity_notifications.xml b/app/src/internal/res/layout/activity_notifications.xml new file mode 100644 index 000000000000..2e1395db2488 --- /dev/null +++ b/app/src/internal/res/layout/activity_notifications.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/internal/res/values/donottranslate.xml b/app/src/internal/res/values/donottranslate.xml index 9268133308f4..da05784c8dc2 100644 --- a/app/src/internal/res/values/donottranslate.xml +++ b/app/src/internal/res/values/donottranslate.xml @@ -38,7 +38,7 @@ Override UserAgent Override Privacy Remote Config URL Custom Tabs - Notifications + Notifications Trigger notifications for testing Click here to customize the Privacy Remote Config URL Load a Custom Tab for a specified URL @@ -89,4 +89,7 @@ Enter a URL Default Browser + + Scheduled Notifications + \ No newline at end of file From 4747a7f7def2847711107539b097c296dd35d86a Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Wed, 20 Nov 2024 18:13:30 +0000 Subject: [PATCH 05/14] add disabled vpn notification --- .../notifications/NotificationsActivity.kt | 10 ++++++++++ .../notifications/NotificationsViewModel.kt | 18 +++++++++++++++++- .../res/layout/activity_notifications.xml | 12 ++++++++++++ app/src/internal/res/values/donottranslate.xml | 1 + 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt index 569f39bf320c..68c47b56029a 100644 --- a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt +++ b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt @@ -82,6 +82,16 @@ class NotificationsActivity : DuckDuckGoActivity() { binding.scheduledNotificationsContainer.addView(it) } } + + viewState.vpnNotifications.forEach { notificationItem -> + buildNotificationItem( + title = notificationItem.title, + subtitle = notificationItem.subtitle, + onClick = { viewModel.onNotificationItemClick(notificationItem) }, + ).also { + binding.vpnNotificationsContainer.addView(it) + } + } } private fun buildNotificationItem( diff --git a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt index c2c558918a1d..81c707246f3d 100644 --- a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt +++ b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt @@ -17,6 +17,7 @@ package com.duckduckgo.app.dev.settings.notifications import android.app.Notification +import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.duckduckgo.anvil.annotations.ContributesViewModel @@ -43,14 +44,17 @@ import kotlinx.coroutines.withContext @ContributesViewModel(ActivityScope::class) class NotificationViewModel @Inject constructor( + private val applicationContext: Context, private val dispatcher: DispatcherProvider, private val schedulableNotificationPluginPoint: PluginPoint, private val factory: NotificationFactory, private val surveyRepository: SurveyRepository, + private val netPDisabledNotificationBuilder: NetPDisabledNotificationBuilder, ) : ViewModel() { data class ViewState( val scheduledNotifications: List = emptyList(), + val vpnNotifications: List = emptyList(), ) { data class NotificationItem( @@ -93,7 +97,19 @@ class NotificationViewModel @Inject constructor( ) } - _viewState.update { it.copy(scheduledNotifications = scheduledNotificationItems) } + val netPDisabledNotificationItem = NotificationItem( + id = 0, + title = "NetP Disabled", + subtitle = "NetP is disabled", + notification = netPDisabledNotificationBuilder.buildVpnAccessRevokedNotification(applicationContext), + ) + + _viewState.update { + it.copy( + scheduledNotifications = scheduledNotificationItems, + vpnNotifications = listOf(netPDisabledNotificationItem), + ) + } } } diff --git a/app/src/internal/res/layout/activity_notifications.xml b/app/src/internal/res/layout/activity_notifications.xml index 2e1395db2488..a9399a7ee689 100644 --- a/app/src/internal/res/layout/activity_notifications.xml +++ b/app/src/internal/res/layout/activity_notifications.xml @@ -48,6 +48,18 @@ android:layout_height="wrap_content" android:orientation="vertical" /> + + + + diff --git a/app/src/internal/res/values/donottranslate.xml b/app/src/internal/res/values/donottranslate.xml index da05784c8dc2..8f0c14a1338a 100644 --- a/app/src/internal/res/values/donottranslate.xml +++ b/app/src/internal/res/values/donottranslate.xml @@ -91,5 +91,6 @@ Scheduled Notifications + VPN Notifications \ No newline at end of file From 61f3c48551d860c1ec8fcfa82dd68d68fdd15a0b Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Wed, 20 Nov 2024 16:43:15 +0000 Subject: [PATCH 06/14] formatting --- .../app/dev/settings/notifications/NotificationsActivity.kt | 5 ++--- .../app/dev/settings/notifications/NotificationsViewModel.kt | 2 +- .../java/com/duckduckgo/app/settings/NewSettingsActivity.kt | 5 +++-- .../com/duckduckgo/subscriptions/impl/RealSubscriptions.kt | 5 ++++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt index 68c47b56029a..04e8e32f8f8b 100644 --- a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt +++ b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsActivity.kt @@ -34,9 +34,9 @@ import com.duckduckgo.common.ui.view.listitem.TwoLineListItem import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.common.utils.notification.checkPermissionAndNotify import com.duckduckgo.di.scopes.ActivityScope +import javax.inject.Inject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import javax.inject.Inject @InjectWith(ActivityScope::class) class NotificationsActivity : DuckDuckGoActivity() { @@ -74,7 +74,6 @@ class NotificationsActivity : DuckDuckGoActivity() { private fun render(viewState: ViewState) { viewState.scheduledNotifications.forEach { notificationItem -> buildNotificationItem( - title = notificationItem.title, subtitle = notificationItem.subtitle, onClick = { viewModel.onNotificationItemClick(notificationItem) }, @@ -108,7 +107,7 @@ class NotificationsActivity : DuckDuckGoActivity() { private fun addNotification( id: Int, - notification: Notification + notification: Notification, ) { NotificationManagerCompat.from(this) .checkPermissionAndNotify(context = this, id = id, notification = notification) diff --git a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt index 81c707246f3d..ea7fa2e67062 100644 --- a/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt +++ b/app/src/internal/java/com/duckduckgo/app/dev/settings/notifications/NotificationsViewModel.kt @@ -61,7 +61,7 @@ class NotificationViewModel @Inject constructor( val id: Int, val title: String, val subtitle: String, - val notification: Notification + val notification: Notification, ) } diff --git a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt index c86fef91f57d..a2511bf77f0e 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt @@ -73,7 +73,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import timber.log.Timber - @InjectWith(ActivityScope::class) @ContributeToActivityStarter(NewSettingsScreenNoParams::class, screenName = "newSettings") class NewSettingsActivity : DuckDuckGoActivity() { @@ -271,7 +270,9 @@ class NewSettingsActivity : DuckDuckGoActivity() { viewsPrivacy.cookiePopupProtectionSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.cookiePopupProtectionEnabled)) viewsPrivacy.cookiePopupProtectionSetting.setItemStatus(CheckListItem.CheckItemStatus.ENABLED) } else { - viewsPrivacy.cookiePopupProtectionSetting.setSecondaryText(getString(com.duckduckgo.app.browser.R.string.cookiePopupProtectionDescription)) + viewsPrivacy.cookiePopupProtectionSetting.setSecondaryText( + getString(com.duckduckgo.app.browser.R.string.cookiePopupProtectionDescription), + ) viewsPrivacy.cookiePopupProtectionSetting.setItemStatus(CheckListItem.CheckItemStatus.DISABLED) } } diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt index 8dc7d3cc73f8..e337c740fe19 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt @@ -175,7 +175,10 @@ class PrivacyProFeatureStore @Inject constructor( } } - private fun SharedPreferences.save(key: String, state: State) { + private fun SharedPreferences.save( + key: String, + state: State, + ) { coroutineScope.launch(dispatcherProvider.io()) { edit(commit = true) { putString(key, stateAdapter.toJson(state)) } } From 3c216573de154f281aa0f49e199d51bb5100bcd0 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Mon, 25 Nov 2024 09:32:50 +0000 Subject: [PATCH 07/14] revert formatting change --- .../com/duckduckgo/subscriptions/impl/RealSubscriptions.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt index e337c740fe19..8dc7d3cc73f8 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt @@ -175,10 +175,7 @@ class PrivacyProFeatureStore @Inject constructor( } } - private fun SharedPreferences.save( - key: String, - state: State, - ) { + private fun SharedPreferences.save(key: String, state: State) { coroutineScope.launch(dispatcherProvider.io()) { edit(commit = true) { putString(key, stateAdapter.toJson(state)) } } From c23f5d1979e93accca03ee96bcbcc837b99c4ad9 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Mon, 25 Nov 2024 16:07:14 +0000 Subject: [PATCH 08/14] Rename SettingsActivity to LegacySettingsActivity --- app/src/main/AndroidManifest.xml | 9 +++++++++ ...sActivity.kt => LegacySettingsActivity.kt} | 19 ++++++------------- ...iewModel.kt => LegacySettingsViewModel.kt} | 4 ++-- ...Test.kt => LegacySettingsViewModelTest.kt} | 8 ++++---- 4 files changed, 21 insertions(+), 19 deletions(-) rename app/src/main/java/com/duckduckgo/app/settings/{SettingsActivity.kt => LegacySettingsActivity.kt} (96%) rename app/src/main/java/com/duckduckgo/app/settings/{SettingsViewModel.kt => LegacySettingsViewModel.kt} (98%) rename app/src/test/java/com/duckduckgo/app/settings/{SettingsViewModelTest.kt => LegacySettingsViewModelTest.kt} (98%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 01ccb40eef5b..86b74a6e7283 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -325,6 +325,15 @@ + + + + + Date: Mon, 25 Nov 2024 16:08:54 +0000 Subject: [PATCH 09/14] fix NewSettingsActivity using wrong ViewModel --- .../java/com/duckduckgo/app/settings/NewSettingsActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt index a2511bf77f0e..dbdab4e7e450 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt @@ -40,7 +40,7 @@ import com.duckduckgo.app.permissions.PermissionsScreenNoParams import com.duckduckgo.app.pixels.AppPixelName import com.duckduckgo.app.pixels.AppPixelName.PRIVACY_PRO_IS_ENABLED_AND_ELIGIBLE import com.duckduckgo.app.privatesearch.PrivateSearchScreenNoParams -import com.duckduckgo.app.settings.SettingsViewModel.Command +import com.duckduckgo.app.settings.NewSettingsViewModel.Command import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily import com.duckduckgo.app.webtrackingprotection.WebTrackingProtectionScreenNoParams @@ -77,7 +77,7 @@ import timber.log.Timber @ContributeToActivityStarter(NewSettingsScreenNoParams::class, screenName = "newSettings") class NewSettingsActivity : DuckDuckGoActivity() { - private val viewModel: SettingsViewModel by bindViewModel() + private val viewModel: NewSettingsViewModel by bindViewModel() private val binding: ActivitySettingsNewBinding by viewBinding() @Inject @@ -302,7 +302,7 @@ class NewSettingsActivity : DuckDuckGoActivity() { } } - private fun updateDefaultBrowserViewVisibility(it: SettingsViewModel.ViewState) { + private fun updateDefaultBrowserViewVisibility(it: NewSettingsViewModel.ViewState) { with(viewsPrivacy.setAsDefaultBrowserSetting) { visibility = if (it.showDefaultBrowserSetting) { if (it.isAppDefaultBrowser) { From 8e46319f18290c5cf99cbd390907f73552a3e062 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Mon, 25 Nov 2024 16:23:14 +0000 Subject: [PATCH 10/14] Create SettingsActivity This will be our bridge to either the Legacy or NewSettingsActivity --- .../app/settings/LegacySettingsActivity.kt | 1 - .../app/settings/SettingsActivity.kt | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt diff --git a/app/src/main/java/com/duckduckgo/app/settings/LegacySettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/LegacySettingsActivity.kt index 9240a33e6577..c154f1d673eb 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/LegacySettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/LegacySettingsActivity.kt @@ -74,7 +74,6 @@ import kotlinx.coroutines.flow.onEach import timber.log.Timber @InjectWith(ActivityScope::class) -@ContributeToActivityStarter(SettingsScreenNoParams::class, screenName = "settings") class LegacySettingsActivity : DuckDuckGoActivity() { private val viewModel: LegacySettingsViewModel by bindViewModel() diff --git a/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt new file mode 100644 index 000000000000..6e61b03c914f --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt @@ -0,0 +1,41 @@ +/* + * 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.settings + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.duckduckgo.anvil.annotations.ContributeToActivityStarter +import com.duckduckgo.anvil.annotations.InjectWith +import com.duckduckgo.browser.api.ui.BrowserScreens.SettingsScreenNoParams +import com.duckduckgo.di.scopes.ActivityScope + +@InjectWith(ActivityScope::class) +@ContributeToActivityStarter(SettingsScreenNoParams::class, screenName = "settings") +class SettingsActivity : DuckDuckGoActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + companion object { + const val LAUNCH_FROM_NOTIFICATION_PIXEL_NAME = "LAUNCH_FROM_NOTIFICATION_PIXEL_NAME" + + fun intent(context: Context): Intent { + return Intent(context, SettingsActivity::class.java) + } + } +} From e9c68ed57718f8d43982fa3ca37ed2864b8b1b51 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Mon, 25 Nov 2024 17:11:56 +0000 Subject: [PATCH 11/14] kill navigation via GlobalActivityStarter No longer needed --- .../java/com/duckduckgo/browser/api/ui/BrowserScreens.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt b/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt index 4ed0d6f394dc..1d66364fa589 100644 --- a/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt +++ b/browser-api/src/main/java/com/duckduckgo/browser/api/ui/BrowserScreens.kt @@ -38,11 +38,6 @@ sealed class BrowserScreens { */ object SettingsScreenNoParams : GlobalActivityStarter.ActivityParams - /** - * Use this model to launch the New Settings screen - */ - object NewSettingsScreenNoParams : GlobalActivityStarter.ActivityParams - /** * Use this model to launch the New Tab Settings screen */ From 1f4714c8f7fb932a80578c5828b5051a2d185daa Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Mon, 25 Nov 2024 17:12:20 +0000 Subject: [PATCH 12/14] add feature toggle to settings-api --- settings/settings-api/build.gradle | 3 +++ .../settings/api/NewSettingsFeature.kt | 25 +++++++++++++++++++ .../settings/impl/NewSettingsTrigger.kt | 11 +++----- 3 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt rename app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt => settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/NewSettingsTrigger.kt (81%) diff --git a/settings/settings-api/build.gradle b/settings/settings-api/build.gradle index 806b9cdd3d98..65619c220529 100644 --- a/settings/settings-api/build.gradle +++ b/settings/settings-api/build.gradle @@ -22,6 +22,9 @@ plugins { apply from: "$rootProject.projectDir/gradle/android-library.gradle" dependencies { + /* Temporary while developing new settings screen */ + implementation project(':feature-toggles-api') + implementation project(':navigation-api') implementation Google.dagger implementation AndroidX.core.ktx diff --git a/settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt b/settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt new file mode 100644 index 000000000000..d1b307144eea --- /dev/null +++ b/settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt @@ -0,0 +1,25 @@ +/* + * 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.settings.api + +import com.duckduckgo.feature.toggles.api.Toggle + +interface NewSettingsFeature { + + @Toggle.DefaultValue(true) + fun self(): Toggle +} diff --git a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt b/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/NewSettingsTrigger.kt similarity index 81% rename from app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt rename to settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/NewSettingsTrigger.kt index 88f041a28061..86f4cd479db0 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsFeature.kt +++ b/settings/settings-impl/src/main/java/com/duckduckgo/settings/impl/NewSettingsTrigger.kt @@ -14,18 +14,15 @@ * limitations under the License. */ -package com.duckduckgo.app.settings +package com.duckduckgo.settings.impl import com.duckduckgo.anvil.annotations.ContributesRemoteFeature import com.duckduckgo.di.scopes.AppScope -import com.duckduckgo.feature.toggles.api.Toggle +import com.duckduckgo.settings.api.NewSettingsFeature @ContributesRemoteFeature( scope = AppScope::class, featureName = "newSettings", + boundType = NewSettingsFeature::class, ) -interface NewSettingsFeature { - - @Toggle.DefaultValue(false) - fun self(): Toggle -} +private interface NewSettingsCodeGenTrigger From 1a810d973b938e2c26c9928c2323dcee45fd213b Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Mon, 25 Nov 2024 17:19:40 +0000 Subject: [PATCH 13/14] add flag to settings activity --- .../duckduckgo/app/settings/NewSettingsActivity.kt | 3 --- .../duckduckgo/app/settings/SettingsActivity.kt | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt index dbdab4e7e450..b88de6a32bf8 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt @@ -25,7 +25,6 @@ import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope -import com.duckduckgo.anvil.annotations.ContributeToActivityStarter import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.app.about.AboutScreenNoParams import com.duckduckgo.app.accessibility.AccessibilityScreens @@ -49,7 +48,6 @@ import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.autoconsent.impl.ui.AutoconsentSettingsActivity import com.duckduckgo.autofill.api.AutofillScreens.AutofillSettingsScreen import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource -import com.duckduckgo.browser.api.ui.BrowserScreens.NewSettingsScreenNoParams import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.view.gone import com.duckduckgo.common.ui.view.listitem.CheckListItem @@ -74,7 +72,6 @@ import kotlinx.coroutines.flow.onEach import timber.log.Timber @InjectWith(ActivityScope::class) -@ContributeToActivityStarter(NewSettingsScreenNoParams::class, screenName = "newSettings") class NewSettingsActivity : DuckDuckGoActivity() { private val viewModel: NewSettingsViewModel by bindViewModel() diff --git a/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt index 6e61b03c914f..2414b4520263 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt @@ -22,15 +22,29 @@ import android.os.Bundle import com.duckduckgo.anvil.annotations.ContributeToActivityStarter import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.browser.api.ui.BrowserScreens.SettingsScreenNoParams +import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.settings.api.NewSettingsFeature +import javax.inject.Inject @InjectWith(ActivityScope::class) @ContributeToActivityStarter(SettingsScreenNoParams::class, screenName = "settings") class SettingsActivity : DuckDuckGoActivity() { + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (newSettingsFeature.self().isEnabled()) { + startActivity(NewSettingsActivity.intent(this)) + } else { + startActivity(LegacySettingsActivity.intent(this)) + } + finish() + } + companion object { const val LAUNCH_FROM_NOTIFICATION_PIXEL_NAME = "LAUNCH_FROM_NOTIFICATION_PIXEL_NAME" From fefed440f8143f636bc8da5557389e1ee0ff7945 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Wed, 27 Nov 2024 08:22:42 +0000 Subject: [PATCH 14/14] set toggle to false --- .../main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt b/settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt index d1b307144eea..31a7694db581 100644 --- a/settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt +++ b/settings/settings-api/src/main/java/com/duckduckgo/settings/api/NewSettingsFeature.kt @@ -20,6 +20,6 @@ import com.duckduckgo.feature.toggles.api.Toggle interface NewSettingsFeature { - @Toggle.DefaultValue(true) + @Toggle.DefaultValue(false) fun self(): Toggle }