From 4023cbe1d145f55eef7f9948c7561c34d5a53a8d Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Thu, 12 Dec 2024 11:06:56 +0100 Subject: [PATCH] Update Settings: Update sub-screens (#5334) Task/Issue URL: https://app.asana.com/0/1207908166761516/1208785622935234/f ### Description Makes design changes to all the settings sub-screens, including: - Copy changes - Padding/Margin changes - Adding/Updating new header images - Rearranging items (Data clearing screen only) ### Steps to test this PR Prerequisites: `newSettings` flag is on Designs: ### _Private Search_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=12993-10537&m=dev) - [x] Open Private Search screen - [x] Screen should default to device transition and no longer crossfade - [x] Check new header image is visible - [x] Check Status Indicator is visible and "Always On" - [x] Check copy has been updated - [x] Check against designs #### UI changes | Before | After | | - | - | Alt Text|Alt Text ### _Web Tracking Protection_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=12993-10580&m=dev) - [x] Open Web Tracking Protection screen - [x] Screen should default to device transition and no longer crossfade - [x] Check new header image is visible - [x] Check Status Indicator is visible and "Always On" - [x] Check copy has been updated - [x] Check against designs #### UI changes | Before | After | | - | - | Alt Text|Alt Text ### _Cookie Pop-Up Protection_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=13010-29805&m=dev) - [x] Open Cookie Pop-Up Protection screen - [x] Screen should default to device transition and no longer crossfade - [x] Check new header image is visible - [x] Check Status Indicator is visible and is "On" or "Off" depending on the toggle - [x] Turn toggle on/off - [x] Check header image changes to either on/off version - [x] Check Status Indicator turns to on/off - [x] Check copy - [x] Press "Learn More" - [x] Tracking Protections website should open - [x] Press back - [x] Check against designs #### UI changes | Before | After | | - | - | | ### _App Tracking Protection_ - [x] Open App Tracking Protection screen - [x] Screen should default to device transition and no longer crossfade - No other changes made to this screen ### _Email Protection_ - [x] Open Email Protection screen - [x] Screen should default to device transition and no longer crossfade - No other changes made to this screen ### _General_ - [x] Open General screen - [x] Screen should default to device transition and no longer crossfade - No other changes made to this screen ### _Sync & Backup_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=13280-39196&m=dev) - [x] Open Sync & Backup screen - [x] Screen should default to device transition and no longer crossfade - [x] Check "Single Device Setup" heading is now "Other Options" - [x] Check against designs #### UI changes | Before | After | | - | - | | ### _Appearance_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=12441-20931&m=dev) - [x] Open Appearance screen - [x] Screen should default to device transition and no longer crossfade - [x] The App Icon should now be aligned to the right of the screen #### UI changes | Before | After | | - | - | | ### _Passwords & Autofill_ - [x] Open Passwords & Autofill screen - [x] Screen should default to device transition and no longer crossfade - [x] The toolbar title should be "Passwords & Autofill" #### UI changes | Before | After | | - | - | | ### _Accessibility_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=11073-14078&m=dev) - [x] Open Accessibility screen - [x] Screen should default to device transition and no longer crossfade - [x] Check screen top padding has been increased #### UI changes | Before | After | | - | - | | ### _Permissions_ - [x] Open Permissions screen - [x] Screen should default to device transition and no longer crossfade - No other changes made to this screen ### _Data Clearing_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=12993-12784&m=dev) - [x] Open Data Clearing screen - [x] Screen should default to device transition and no longer crossfade - [x] Order of items should be 1. "Fire Button Animation" 2. Divider 3. "Fireproof Sites" 4. "Automatically Clear Data..." 5. "Clear On..." - [x] Check against designs #### UI changes | Before | After | | - | - | | ### _Duck Player_ - [ ] Open Duck Player screen - [ ] Screen should default to device transition and no longer crossfade - No other changes made to this screen ### _About_ - [Designs](https://www.figma.com/design/BOHDESHODUXK7wSRNBOHdu/%F0%9F%A4%96-Android-Components?node-id=12993-10920&m=dev) - [ ] Open About screen - [ ] Screen should default to device transition and no longer crossfade - [ ] Title in toolbar should be "About" - [ ] Check about copy matches designs - [ ] Check against designs #### UI changes | Before | After | | - | - | | _LegacySettings Screen_ Prerequisites: `newSettings` flag is off - [ ] Smoke test, run through each item an ensure there are no major visual changes e.g. two header images instead of 1 ## Demo https://github.com/user-attachments/assets/8c256538-816c-4ef5-a01b-d31260f6cb41 --- .../app/about/AboutDuckDuckGoActivity.kt | 30 +++- .../app/firebutton/FireButtonActivity.kt | 27 +++- .../privatesearch/PrivateSearchActivity.kt | 19 +++ .../app/settings/NewSettingsActivity.kt | 128 ++++-------------- .../WebTrackingProtectionActivity.kt | 55 +++++++- app/src/main/res/drawable/search_ok_128.xml | 34 +++++ .../main/res/drawable/shield_check_128.xml | 29 ++++ .../activity_accessibility_settings.xml | 2 +- .../main/res/layout/activity_appearance.xml | 4 +- .../res/layout/activity_data_clearing.xml | 77 +++++++++++ .../res/layout/activity_private_search.xml | 60 +++++++- .../activity_web_tracking_protection.xml | 55 +++++++- .../res/layout/content_about_duck_duck_go.xml | 32 +++-- .../layout/content_settings_new_privacy.xml | 4 +- app/src/main/res/values/strings.xml | 5 + autoconsent/autoconsent-impl/build.gradle | 1 + .../impl/ui/AutoconsentSettingsActivity.kt | 61 ++++++++- .../main/res/drawable/cookie_popups_128.xml | 46 +++++++ .../res/drawable/cookie_popups_check_128.xml | 45 ++++++ .../layout/activity_autoconsent_settings.xml | 57 +++++++- .../main/res/values/strings-autoconsent.xml | 3 +- autofill/autofill-impl/build.gradle | 1 + .../management/AutofillManagementActivity.kt | 10 +- .../main/res/values/strings-autofill-impl.xml | 1 + .../common/ui/view/StatusIndicator.kt | 44 ------ .../common/ui/view/StatusIndicatorView.kt | 87 ++++++++++++ .../ui/view/listitem/SettingsListItem.kt | 11 +- .../main/res/layout/component_settings.xml | 22 ++- .../res/layout/view_settings_list_item.xml | 2 +- .../res/values/attrs-settings-list_item.xml | 2 +- .../attrs-settings-status-indicator.xml | 29 ++++ .../src/main/res/values/strings-common-ui.xml | 7 +- sync/sync-impl/build.gradle | 1 + .../duckduckgo/sync/impl/ui/SyncActivity.kt | 9 ++ .../main/res/layout/view_sync_disabled.xml | 3 + .../src/main/res/values/strings-sync.xml | 3 +- 36 files changed, 808 insertions(+), 198 deletions(-) create mode 100644 app/src/main/res/drawable/search_ok_128.xml create mode 100644 app/src/main/res/drawable/shield_check_128.xml create mode 100644 app/src/main/res/layout/activity_data_clearing.xml create mode 100644 autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_128.xml create mode 100644 autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_check_128.xml delete mode 100644 common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicator.kt create mode 100644 common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicatorView.kt create mode 100644 common/common-ui/src/main/res/values/attrs-settings-status-indicator.xml diff --git a/app/src/main/java/com/duckduckgo/app/about/AboutDuckDuckGoActivity.kt b/app/src/main/java/com/duckduckgo/app/about/AboutDuckDuckGoActivity.kt index be9f0eab0280..f4a81f0204f0 100644 --- a/app/src/main/java/com/duckduckgo/app/about/AboutDuckDuckGoActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/about/AboutDuckDuckGoActivity.kt @@ -27,6 +27,7 @@ import android.text.style.ForegroundColorSpan import android.text.style.UnderlineSpan import android.view.View import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -80,7 +81,13 @@ class AboutDuckDuckGoActivity : DuckDuckGoActivity() { setContentView(binding.root) setupToolbar(binding.includeToolbar.toolbar) - binding.includeContent.aboutProvideFeedback.isGone = settingsFeature.self().isEnabled() + if (settingsFeature.self().isEnabled()) { + supportActionBar?.setTitle(R.string.aboutActivityTitleNew) + binding.includeContent.aboutTextNew.isVisible = true + + binding.includeContent.aboutText.isGone = true + binding.includeContent.aboutProvideFeedback.isGone = true + } configureUiEventHandlers() observeViewModel() @@ -93,14 +100,27 @@ class AboutDuckDuckGoActivity : DuckDuckGoActivity() { } private fun configureClickableLinks() { - with(binding.includeContent.aboutText) { - text = addClickableLinks() - movementMethod = LinkMovementMethod.getInstance() + if (settingsFeature.self().isEnabled()) { + with(binding.includeContent.aboutTextNew) { + text = addClickableLinks() + movementMethod = LinkMovementMethod.getInstance() + } + } else { + with(binding.includeContent.aboutText) { + text = addClickableLinks() + movementMethod = LinkMovementMethod.getInstance() + } } } private fun addClickableLinks(): SpannableString { - val fullText = getText(R.string.aboutDescription) as SpannedString + val fullText = getText( + if (settingsFeature.self().isEnabled()) { + R.string.aboutDescriptionNew + } else { + R.string.aboutDescription + }, + ) as SpannedString val spannableString = SpannableString(fullText) val annotations = fullText.getSpans(0, fullText.length, Annotation::class.java) diff --git a/app/src/main/java/com/duckduckgo/app/firebutton/FireButtonActivity.kt b/app/src/main/java/com/duckduckgo/app/firebutton/FireButtonActivity.kt index 6db659806162..adf67024a9fd 100644 --- a/app/src/main/java/com/duckduckgo/app/firebutton/FireButtonActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/firebutton/FireButtonActivity.kt @@ -25,6 +25,7 @@ import androidx.lifecycle.lifecycleScope import com.duckduckgo.anvil.annotations.ContributeToActivityStarter import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.app.browser.R +import com.duckduckgo.app.browser.databinding.ActivityDataClearingBinding import com.duckduckgo.app.browser.databinding.ActivityFireButtonBinding import com.duckduckgo.app.fire.fireproofwebsite.ui.FireproofWebsitesActivity import com.duckduckgo.app.firebutton.FireButtonViewModel.AutomaticallyClearData @@ -43,6 +44,7 @@ import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.view.dialog.RadioListAlertDialogBuilder import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.settings.api.NewSettingsFeature import javax.inject.Inject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -58,20 +60,35 @@ class FireButtonActivity : DuckDuckGoActivity() { @Inject lateinit var appBuildConfig: AppBuildConfig + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + private val viewModel: FireButtonViewModel by bindViewModel() - private val binding: ActivityFireButtonBinding by viewBinding() + private val legacyBinding: ActivityFireButtonBinding by viewBinding() // TODO remove + private val binding: ActivityDataClearingBinding by viewBinding() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(binding.root) - setupToolbar(binding.includeToolbar.toolbar) + if (newSettingsFeature.self().isEnabled()) { + setContentView(binding.root) + setupToolbar(binding.includeToolbar.toolbar) + supportActionBar?.setTitle(R.string.dataClearingActivityTitle) + } else { + setContentView(legacyBinding.root) + setupToolbar(legacyBinding.includeToolbar.toolbar) + } configureUiEventHandlers() observeViewModel() } private fun configureUiEventHandlers() { + legacyBinding.fireproofWebsites.setClickListener { viewModel.onFireproofWebsitesClicked() } + legacyBinding.automaticallyClearWhatSetting.setClickListener { viewModel.onAutomaticallyClearWhatClicked() } + legacyBinding.automaticallyClearWhenSetting.setClickListener { viewModel.onAutomaticallyClearWhenClicked() } + legacyBinding.selectedFireAnimationSetting.setClickListener { viewModel.userRequestedToChangeFireAnimation() } + binding.fireproofWebsites.setClickListener { viewModel.onFireproofWebsitesClicked() } binding.automaticallyClearWhatSetting.setClickListener { viewModel.onAutomaticallyClearWhatClicked() } binding.automaticallyClearWhenSetting.setClickListener { viewModel.onAutomaticallyClearWhenClicked() } @@ -96,17 +113,21 @@ class FireButtonActivity : DuckDuckGoActivity() { private fun updateAutomaticClearDataOptions(automaticallyClearData: AutomaticallyClearData) { val clearWhatSubtitle = getString(automaticallyClearData.clearWhatOption.nameStringResourceId()) + legacyBinding.automaticallyClearWhatSetting.setSecondaryText(clearWhatSubtitle) binding.automaticallyClearWhatSetting.setSecondaryText(clearWhatSubtitle) val clearWhenSubtitle = getString(automaticallyClearData.clearWhenOption.nameStringResourceId()) + legacyBinding.automaticallyClearWhenSetting.setSecondaryText(clearWhenSubtitle) binding.automaticallyClearWhenSetting.setSecondaryText(clearWhenSubtitle) val whenOptionEnabled = automaticallyClearData.clearWhenOptionEnabled + legacyBinding.automaticallyClearWhenSetting.isEnabled = whenOptionEnabled binding.automaticallyClearWhenSetting.isEnabled = whenOptionEnabled } private fun updateSelectedFireAnimation(fireAnimation: FireAnimation) { val subtitle = getString(fireAnimation.nameResId) + legacyBinding.selectedFireAnimationSetting.setSecondaryText(subtitle) binding.selectedFireAnimationSetting.setSecondaryText(subtitle) } diff --git a/app/src/main/java/com/duckduckgo/app/privatesearch/PrivateSearchActivity.kt b/app/src/main/java/com/duckduckgo/app/privatesearch/PrivateSearchActivity.kt index 21b7f42996f8..bd6ae857e0fd 100644 --- a/app/src/main/java/com/duckduckgo/app/privatesearch/PrivateSearchActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/privatesearch/PrivateSearchActivity.kt @@ -18,6 +18,7 @@ package com.duckduckgo.app.privatesearch import android.os.Bundle import android.widget.CompoundButton +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle @@ -32,6 +33,7 @@ import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.duckduckgo.settings.api.NewSettingsFeature import javax.inject.Inject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -43,6 +45,9 @@ class PrivateSearchActivity : DuckDuckGoActivity() { @Inject lateinit var globalActivityStarter: GlobalActivityStarter + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + private val viewModel: PrivateSearchViewModel by bindViewModel() private val binding: ActivityPrivateSearchBinding by viewBinding() @@ -60,6 +65,20 @@ class PrivateSearchActivity : DuckDuckGoActivity() { setContentView(binding.root) setupToolbar(binding.includeToolbar.toolbar) + if (newSettingsFeature.self().isEnabled()) { + with(binding) { + privateSearchHeaderImage.isGone = true + privateSearchTitle.isGone = true + privateSearchDescription.isGone = true + privateSearchHeadingSearchSettings.isGone = true + + privateSearchHeaderImageNew.isVisible = true + privateSearchTitleNew.isVisible = true + statusIndicator.isVisible = true + privateSearchDescriptionNew.isVisible = true + } + } + configureUiEventHandlers() observeViewModel() } 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 211d2cda4c4b..23c911930f0b 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt @@ -16,7 +16,6 @@ package com.duckduckgo.app.settings -import android.app.ActivityOptions import android.content.Context import android.content.Intent import android.os.Bundle @@ -81,6 +80,7 @@ import com.duckduckgo.internal.features.api.InternalFeaturePlugin import com.duckduckgo.mobile.android.app.tracking.ui.AppTrackingProtectionScreens.AppTrackerActivityWithEmptyParams import com.duckduckgo.mobile.android.app.tracking.ui.AppTrackingProtectionScreens.AppTrackerOnboardingActivityWithEmptyParamsParams import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams import com.duckduckgo.settings.api.DuckPlayerSettingsPlugin import com.duckduckgo.settings.api.ProSettingsPlugin import com.duckduckgo.subscriptions.api.PrivacyProFeedbackScreens.GeneralPrivacyProFeedbackScreenNoParams @@ -305,29 +305,28 @@ class NewSettingsActivity : DuckDuckGoActivity() { viewsPrivacy.cookiePopupProtectionSetting.setStatus(isOn = enabled) } - private fun processCommand(it: Command?) { + private fun processCommand(it: Command) { when (it) { is LaunchDefaultBrowser -> launchDefaultAppScreen() - is LaunchAutofillSettings -> launchAutofillSettings() - is LaunchAccessibilitySettings -> launchAccessibilitySettings() - is LaunchAppTPTrackersScreen -> launchAppTPTrackersScreen() - is LaunchAppTPOnboarding -> launchAppTPOnboardingScreen() - is LaunchEmailProtection -> launchEmailProtectionScreen(it.url) - is LaunchEmailProtectionNotSupported -> launchEmailProtectionNotSupported() + is LaunchAutofillSettings -> launchScreen(AutofillSettingsScreen(source = AutofillSettingsLaunchSource.SettingsActivity)) + is LaunchAccessibilitySettings -> launchScreen(AccessibilityScreens.Default) + is LaunchAppTPTrackersScreen -> launchScreen(AppTrackerActivityWithEmptyParams) + is LaunchAppTPOnboarding -> launchScreen(AppTrackerOnboardingActivityWithEmptyParamsParams) + is LaunchEmailProtection -> launchActivityAndFinish(BrowserActivity.intent(this, it.url, interstitialScreen = true)) + is LaunchEmailProtectionNotSupported -> launchScreen(EmailProtectionUnsupportedScreenNoParams) is LaunchAddHomeScreenWidget -> launchAddHomeScreenWidget() - is LaunchSyncSettings -> launchSyncSettings() - is LaunchPrivateSearchWebPage -> launchPrivateSearchScreen() - is LaunchWebTrackingProtectionScreen -> launchWebTrackingProtectionScreen() - is LaunchCookiePopupProtectionScreen -> launchCookiePopupProtectionScreen() - is LaunchFireButtonScreen -> launchFireButtonScreen() - is LaunchPermissionsScreen -> launchPermissionsScreen() - is LaunchAppearanceScreen -> launchAppearanceScreen() - is LaunchAboutScreen -> launchAboutScreen() - is LaunchGeneralSettingsScreen -> launchGeneralSettingsScreen() + is LaunchSyncSettings -> launchScreen(SyncActivityWithEmptyParams) + is LaunchPrivateSearchWebPage -> launchScreen(PrivateSearchScreenNoParams) + is LaunchWebTrackingProtectionScreen -> launchScreen(WebTrackingProtectionScreenNoParams) + is LaunchCookiePopupProtectionScreen -> launchActivity(AutoconsentSettingsActivity.intent(this)) + is LaunchFireButtonScreen -> launchScreen(FireButtonScreenNoParams) + is LaunchPermissionsScreen -> launchScreen(PermissionsScreenNoParams) + is LaunchAppearanceScreen -> launchScreen(AppearanceScreen.Default) + is LaunchAboutScreen -> launchScreen(AboutScreenNoParams) + is LaunchGeneralSettingsScreen -> launchScreen(GeneralSettingsScreenNoParams) is LaunchFeedback -> launchFeedback() - is LaunchPproUnifiedFeedback -> launchPproUnifiedFeedback() - is LaunchOtherPlatforms -> launchOtherPlatforms() - null -> TODO() + is LaunchPproUnifiedFeedback -> launchScreen(GeneralPrivacyProFeedbackScreenNoParams) + is LaunchOtherPlatforms -> launchActivityAndFinish(BrowserActivity.intent(context = this, queryExtra = OTHER_PLATFORMS_URL)) } } @@ -350,40 +349,17 @@ class NewSettingsActivity : DuckDuckGoActivity() { launchDefaultAppActivity() } - private fun launchAutofillSettings() { - val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() - globalActivityStarter.start(this, AutofillSettingsScreen(source = AutofillSettingsLaunchSource.SettingsActivity), options) + private fun launchScreen(activityParams: ActivityParams) { + globalActivityStarter.start(this, activityParams) } - private fun launchAccessibilitySettings() { - val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle() - globalActivityStarter.start(this, AccessibilityScreens.Default, options) + private fun launchActivity(intent: Intent) { + startActivity(intent) } - 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 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 launchActivityAndFinish(intent: Intent) { + launchActivity(intent) + finish() } private fun launchAddHomeScreenWidget() { @@ -391,62 +367,10 @@ class NewSettingsActivity : DuckDuckGoActivity() { 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) - } - private fun launchFeedback() { feedbackFlow.launch(null) } - private fun launchPproUnifiedFeedback() { - globalActivityStarter.start( - this, - GeneralPrivacyProFeedbackScreenNoParams, - ) - } - - private fun launchOtherPlatforms() { - startActivity(BrowserActivity.intent(context = this, queryExtra = OTHER_PLATFORMS_URL)) - finish() - } - companion object { const val LAUNCH_FROM_NOTIFICATION_PIXEL_NAME = "LAUNCH_FROM_NOTIFICATION_PIXEL_NAME" diff --git a/app/src/main/java/com/duckduckgo/app/webtrackingprotection/WebTrackingProtectionActivity.kt b/app/src/main/java/com/duckduckgo/app/webtrackingprotection/WebTrackingProtectionActivity.kt index 0b3e9f997f97..284b6244fafa 100644 --- a/app/src/main/java/com/duckduckgo/app/webtrackingprotection/WebTrackingProtectionActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/webtrackingprotection/WebTrackingProtectionActivity.kt @@ -19,10 +19,13 @@ package com.duckduckgo.app.webtrackingprotection import android.app.ActivityOptions import android.os.Bundle import android.text.SpannableStringBuilder +import android.text.TextPaint import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.text.style.URLSpan import android.view.View +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -35,10 +38,13 @@ import com.duckduckgo.app.privacy.ui.AllowListActivity import com.duckduckgo.app.webtrackingprotection.WebTrackingProtectionViewModel.Command import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams import com.duckduckgo.common.ui.DuckDuckGoActivity +import com.duckduckgo.common.ui.view.getColorFromAttr import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.common.utils.extensions.html import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.mobile.android.R as CommonR import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.duckduckgo.settings.api.NewSettingsFeature import javax.inject.Inject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -50,13 +56,25 @@ class WebTrackingProtectionActivity : DuckDuckGoActivity() { @Inject lateinit var globalActivityStarter: GlobalActivityStarter + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + private val viewModel: WebTrackingProtectionViewModel by bindViewModel() private val binding: ActivityWebTrackingProtectionBinding by viewBinding() + // TODO eligible for extraction and use in AutoConsent as well when removing old settings private val clickableSpan = object : ClickableSpan() { override fun onClick(widget: View) { viewModel.onLearnMoreSelected() } + + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + if (newSettingsFeature.self().isEnabled()) { + ds.color = getColorFromAttr(CommonR.attr.daxColorAccentBlue) + ds.isUnderlineText = false + } + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -65,6 +83,19 @@ class WebTrackingProtectionActivity : DuckDuckGoActivity() { setContentView(binding.root) setupToolbar(binding.includeToolbar.toolbar) + if (newSettingsFeature.self().isEnabled()) { + with(binding) { + webTrackingProtectionHeaderImage.isGone = true + webTrackingProtectionTitle.isGone = true + webTrackingProtectionDescription.isGone = true + + webTrackingProtectionHeaderImageNew.isVisible = true + webTrackingProtectionTitleNew.isVisible = true + statusIndicator.isVisible = true + webTrackingProtectionDescriptionNew.isVisible = true + } + } + configureUiEventHandlers() configureClickableLink() observeViewModel() @@ -76,11 +107,20 @@ class WebTrackingProtectionActivity : DuckDuckGoActivity() { } private fun configureClickableLink() { - val htmlGPCText = getString(R.string.webTrackingProtectionDescription).html(this) + val htmlGPCText = getString( + if (newSettingsFeature.self().isEnabled()) { + R.string.webTrackingProtectionDescriptionNew + } else { + R.string.webTrackingProtectionDescription + }, + ).html(this) val gpcSpannableString = SpannableStringBuilder(htmlGPCText) val urlSpans = htmlGPCText.getSpans(0, htmlGPCText.length, URLSpan::class.java) urlSpans?.forEach { gpcSpannableString.apply { + if (newSettingsFeature.self().isEnabled()) { + insert(getSpanStart(it), "\n") + } setSpan( clickableSpan, gpcSpannableString.getSpanStart(it), @@ -91,9 +131,16 @@ class WebTrackingProtectionActivity : DuckDuckGoActivity() { trim() } } - binding.webTrackingProtectionDescription.apply { - text = gpcSpannableString - movementMethod = LinkMovementMethod.getInstance() + if (newSettingsFeature.self().isEnabled()) { + binding.webTrackingProtectionDescriptionNew.apply { + text = gpcSpannableString + movementMethod = LinkMovementMethod.getInstance() + } + } else { + binding.webTrackingProtectionDescription.apply { + text = gpcSpannableString + movementMethod = LinkMovementMethod.getInstance() + } } } diff --git a/app/src/main/res/drawable/search_ok_128.xml b/app/src/main/res/drawable/search_ok_128.xml new file mode 100644 index 000000000000..4ffb1e231255 --- /dev/null +++ b/app/src/main/res/drawable/search_ok_128.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/shield_check_128.xml b/app/src/main/res/drawable/shield_check_128.xml new file mode 100644 index 000000000000..13dd3d75d02b --- /dev/null +++ b/app/src/main/res/drawable/shield_check_128.xml @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/activity_accessibility_settings.xml b/app/src/main/res/layout/activity_accessibility_settings.xml index 4bea376f5df8..c51d17b26a5d 100644 --- a/app/src/main/res/layout/activity_accessibility_settings.xml +++ b/app/src/main/res/layout/activity_accessibility_settings.xml @@ -36,7 +36,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:paddingBottom="@dimen/keyline_4"> + android:paddingVertical="@dimen/keyline_4"> + tools:srcCompat="@drawable/ic_ddg_logo" /> diff --git a/app/src/main/res/layout/activity_data_clearing.xml b/app/src/main/res/layout/activity_data_clearing.xml new file mode 100644 index 000000000000..c97fc1ff183a --- /dev/null +++ b/app/src/main/res/layout/activity_data_clearing.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_private_search.xml b/app/src/main/res/layout/activity_private_search.xml index 86513596b64c..7946d3b81f42 100644 --- a/app/src/main/res/layout/activity_private_search.xml +++ b/app/src/main/res/layout/activity_private_search.xml @@ -46,7 +46,19 @@ android:layout_marginTop="@dimen/keyline_3" android:padding="@dimen/keyline_2" app:srcCompat="@drawable/private_search_header_image" - tools:ignore="ContentDescription" /> + tools:ignore="ContentDescription" + tools:visibility="gone" /> + + + app:typography="h2" + tools:visibility="gone" /> + + + + app:typography="body2" + tools:visibility="gone" /> + + + app:primaryText="@string/privateSearchHeadingSearchSettings" + tools:visibility="gone" /> + tools:ignore="ContentDescription" + tools:visibility="gone" /> + + + app:typography="h2" + tools:visibility="gone" /> + + + + app:typography="body2" + tools:visibility="gone" /> + + + tools:ignore="HardcodedText" + tools:text="..." /> + + + tools:text="Long description would go here\n\nLots and lots of words" + tools:visibility="visible" /> + android:layout_height="wrap_content" + android:layout_marginVertical="@dimen/keyline_2" /> @@ -46,7 +46,7 @@ android:id="@+id/webTrackingProtectionSetting" android:layout_width="match_parent" android:layout_height="wrap_content" - app:isOn="true" + app:indicatorStatus="on" app:leadingIcon="@drawable/ic_shield_color_24" app:primaryText="@string/settingsWebTrackingProtectionTitle" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6018634f06fc..05fe9b410ec6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,6 +191,7 @@ Web Tracking Protection Web Tracking Protection is Enabled Learn More]]> + Learn More]]> Cookie Pop-Up Protection @@ -199,6 +200,7 @@ Fire Button + Data Clearing Permissions @@ -210,6 +212,7 @@ Save Automatically Clear… + Automatically Clear Data… Automatically clear… None Tabs @@ -226,8 +229,10 @@ About DuckDuckGo + About Welcome to the Duck Side! DuckDuckGo is the independent Internet privacy company founded in 2008 for anyone who’s tired of being tracked online and wants an easy solution. We’re proof you can get real privacy protection online without tradeoffs.\n\nThe DuckDuckGo browser comes with the features you expect from a go-to browser, like bookmarks, tabs, passwords, and more, plus over a dozen powerful privacy protections not offered in most popular browsers by default. This uniquely comprehensive set of privacy protections helps protect your online activities, from searching to browsing, emailing, and more.\n\nOur privacy protections work without having to know anything about the technical details or deal with complicated settings. All you have to do is switch your browser to DuckDuckGo across all your devices and you get privacy by default.\n\nBut if you do want a peek under the hood, you can find more information about how DuckDuckGo privacy protections work on our help pages + DuckDuckGo is the independent Internet privacy company founded in 2008 for anyone who\'s tired of being tracked online and wants an easy solution. We\'re proof you can get real privacy protection online without tradeoffs.\n\nThe DuckDuckGo browser comes with the features you expect from a go-to browser, like bookmarks, tabs, passwords, and more, plus over a dozen powerful privacy protections not offered in most popular browsers by default. This uniquely comprehensive set of privacy protections helps protect your online activities, from searching to browsing, emailing, and more.\n\nOur privacy protections work without having to know anything about the technical details or deal with complicated settings. All you have to do is switch your browser to DuckDuckGo across all your devices and you get privacy by default.\n\nBut if you do want a peek under the hood, you can find more information about how DuckDuckGo privacy protections work on our help pages No Suggestions diff --git a/autoconsent/autoconsent-impl/build.gradle b/autoconsent/autoconsent-impl/build.gradle index b8b27b0c9dff..cbb0139f52a1 100644 --- a/autoconsent/autoconsent-impl/build.gradle +++ b/autoconsent/autoconsent-impl/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation project(path: ':statistics-api') implementation project(path: ':browser-api') implementation project(path: ':navigation-api') + implementation project(path: ':settings-api') // temporary until we release new settings implementation AndroidX.appCompat implementation JakeWharton.timber diff --git a/autoconsent/autoconsent-impl/src/main/java/com/duckduckgo/autoconsent/impl/ui/AutoconsentSettingsActivity.kt b/autoconsent/autoconsent-impl/src/main/java/com/duckduckgo/autoconsent/impl/ui/AutoconsentSettingsActivity.kt index 0a05e398a8f1..09ae7b3beff5 100644 --- a/autoconsent/autoconsent-impl/src/main/java/com/duckduckgo/autoconsent/impl/ui/AutoconsentSettingsActivity.kt +++ b/autoconsent/autoconsent-impl/src/main/java/com/duckduckgo/autoconsent/impl/ui/AutoconsentSettingsActivity.kt @@ -21,11 +21,15 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.text.SpannableStringBuilder +import android.text.TextPaint import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.text.style.URLSpan import android.view.View import android.widget.CompoundButton +import androidx.core.content.ContextCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -39,7 +43,9 @@ import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.common.utils.extensions.html import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.mobile.android.R as CommonR import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.duckduckgo.settings.api.NewSettingsFeature import javax.inject.Inject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -50,6 +56,9 @@ class AutoconsentSettingsActivity : DuckDuckGoActivity() { @Inject lateinit var globalActivityStarter: GlobalActivityStarter + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + private val binding: ActivityAutoconsentSettingsBinding by viewBinding() private val viewModel: AutoconsentSettingsViewModel by bindViewModel() @@ -65,6 +74,14 @@ class AutoconsentSettingsActivity : DuckDuckGoActivity() { override fun onClick(widget: View) { viewModel.onLearnMoreSelected() } + + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + if (newSettingsFeature.self().isEnabled()) { + ds.color = ContextCompat.getColor(applicationContext, CommonR.color.blue50) + ds.isUnderlineText = false + } + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -73,6 +90,17 @@ class AutoconsentSettingsActivity : DuckDuckGoActivity() { setContentView(binding.root) setupToolbar(toolbar) + if (newSettingsFeature.self().isEnabled()) { + with(binding) { + autoconsentHeaderImage.isVisible = true + autoconsentTitle.isVisible = true + autoconsentStatusIndicator.isVisible = true + autoconsentDescription.isGone = true + autoconsentDescriptionNew.isVisible = true + divider.isVisible = true + } + } + configureUiEventHandlers() configureClickableLink() observeViewModel() @@ -93,6 +121,14 @@ class AutoconsentSettingsActivity : DuckDuckGoActivity() { } private fun render(viewState: ViewState) { + if (newSettingsFeature.self().isEnabled()) { + with(binding) { + autoconsentHeaderImage.setImageResource( + if (viewState.autoconsentEnabled) R.drawable.cookie_popups_check_128 else R.drawable.cookie_popups_128, + ) + autoconsentStatusIndicator.setStatus(viewState.autoconsentEnabled) + } + } binding.autoconsentToggle.quietlySetIsChecked(viewState.autoconsentEnabled, autoconsentToggleListener) } @@ -108,11 +144,20 @@ class AutoconsentSettingsActivity : DuckDuckGoActivity() { } private fun configureClickableLink() { - val htmlText = getString(R.string.autoconsentDescription).html(this) + val htmlText = getString( + if (newSettingsFeature.self().isEnabled()) { + R.string.autoconsentDescriptionNew + } else { + R.string.autoconsentDescription + }, + ).html(this) val spannableString = SpannableStringBuilder(htmlText) val urlSpans = htmlText.getSpans(0, htmlText.length, URLSpan::class.java) urlSpans?.forEach { spannableString.apply { + if (newSettingsFeature.self().isEnabled()) { + insert(spannableString.getSpanStart(it), "\n") + } setSpan( clickableSpan, spannableString.getSpanStart(it), @@ -123,9 +168,17 @@ class AutoconsentSettingsActivity : DuckDuckGoActivity() { trim() } } - binding.autoconsentDescription.apply { - text = spannableString - movementMethod = LinkMovementMethod.getInstance() + if (newSettingsFeature.self().isEnabled()) { + binding.autoconsentDescriptionNew.apply { + text = spannableString + movementMethod = LinkMovementMethod.getInstance() + } + return + } else { + binding.autoconsentDescription.apply { + text = spannableString + movementMethod = LinkMovementMethod.getInstance() + } } } diff --git a/autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_128.xml b/autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_128.xml new file mode 100644 index 000000000000..9b346e4c3502 --- /dev/null +++ b/autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_128.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + diff --git a/autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_check_128.xml b/autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_check_128.xml new file mode 100644 index 000000000000..9e56dc3b2248 --- /dev/null +++ b/autoconsent/autoconsent-impl/src/main/res/drawable/cookie_popups_check_128.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + diff --git a/autoconsent/autoconsent-impl/src/main/res/layout/activity_autoconsent_settings.xml b/autoconsent/autoconsent-impl/src/main/res/layout/activity_autoconsent_settings.xml index 8f177e40b9ff..59718f4b6fc6 100644 --- a/autoconsent/autoconsent-impl/src/main/res/layout/activity_autoconsent_settings.xml +++ b/autoconsent/autoconsent-impl/src/main/res/layout/activity_autoconsent_settings.xml @@ -37,6 +37,38 @@ android:orientation="vertical" android:paddingBottom="@dimen/keyline_5"> + + + + + + + app:typography="body2" + tools:visibility="gone" /> + + + + - + Cookie Pop-Up Protection Learn More]]> + Learn More]]> Let DuckDuckGo handle cookie pop-ups \ No newline at end of file diff --git a/autofill/autofill-impl/build.gradle b/autofill/autofill-impl/build.gradle index 6e5dc612d6db..88287d919531 100644 --- a/autofill/autofill-impl/build.gradle +++ b/autofill/autofill-impl/build.gradle @@ -45,6 +45,7 @@ dependencies { implementation project(':new-tab-page-api') implementation project(':data-store-api') testImplementation project(':feature-toggles-test') + implementation project(path: ':settings-api') // temporary until we release new settings anvil project(path: ':anvil-compiler') implementation project(path: ':anvil-annotations') diff --git a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt index 3bcf765c69c8..6db0d81b1444 100644 --- a/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt +++ b/autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt @@ -74,6 +74,7 @@ import com.duckduckgo.common.ui.view.showKeyboard import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.navigation.api.getActivityParams +import com.duckduckgo.settings.api.NewSettingsFeature import com.google.android.material.snackbar.Snackbar import javax.inject.Inject import kotlinx.coroutines.launch @@ -94,6 +95,9 @@ class AutofillManagementActivity : DuckDuckGoActivity(), PasswordsScreenPromotio @Inject lateinit var pixel: Pixel + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -324,7 +328,11 @@ class AutofillManagementActivity : DuckDuckGoActivity(), PasswordsScreenPromotio } private fun resetToolbar() { - setTitle(R.string.autofillManagementScreenTitle) + if (newSettingsFeature.self().isEnabled()) { + setTitle(R.string.autofillManagementScreenTitleNew) + } else { + setTitle(R.string.autofillManagementScreenTitle) + } binding.toolbar.menu.clear() hideSearchBar() supportActionBar?.setHomeAsUpIndicator(com.duckduckgo.mobile.android.R.drawable.ic_arrow_left_24) diff --git a/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml b/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml index efda52b0e480..092dce600281 100644 --- a/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml +++ b/autofill/autofill-impl/src/main/res/values/strings-autofill-impl.xml @@ -120,6 +120,7 @@ Exit Setup Passwords + Passwords & Autofill No passwords saved yet Save and Autofill Passwords Passwords are encrypted. Nobody but you can see them, not even us. Learn More diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicator.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicator.kt deleted file mode 100644 index 48b4be2b75a0..000000000000 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicator.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.common.ui.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.LinearLayout -import com.duckduckgo.common.ui.viewbinding.viewBinding -import com.duckduckgo.mobile.android.databinding.ViewStatusIndicatorBinding - -class StatusIndicator @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : LinearLayout(context, attrs, defStyleAttr) { - - private val binding: ViewStatusIndicatorBinding by viewBinding() - - fun setStatus(isOn: Boolean) { - if (isOn) { - binding.icon.isEnabled = true - // TODO copy changes - binding.label.text = "On" - } else { - binding.icon.isEnabled = false - // TODO copy changes - binding.label.text = "Off" - } - } -} diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicatorView.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicatorView.kt new file mode 100644 index 000000000000..4f4b6996982a --- /dev/null +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/StatusIndicatorView.kt @@ -0,0 +1,87 @@ +/* + * 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.common.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import com.duckduckgo.common.ui.viewbinding.viewBinding +import com.duckduckgo.mobile.android.R +import com.duckduckgo.mobile.android.databinding.ViewStatusIndicatorBinding + +class StatusIndicatorView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : LinearLayout(context, attrs, defStyleAttr) { + + private val binding: ViewStatusIndicatorBinding by viewBinding() + + init { + context.obtainStyledAttributes( + attrs, + R.styleable.StatusIndicator, + 0, + 0, + ).apply { + + val status = Status.from(getInt(R.styleable.StatusIndicator_indicatorStatus, 0)) + setStatus(status) + + recycle() + } + } + + fun setStatus(status: Status) { + when (status) { + Status.ALWAYS_ON -> { + binding.icon.isEnabled = true + binding.label.text = context.getString(R.string.alwaysOn) + } + Status.ON -> { + binding.icon.isEnabled = true + binding.label.text = context.getString(R.string.on) + } + Status.OFF -> { + binding.icon.isEnabled = false + binding.label.text = context.getString(R.string.off) + } + } + } + + fun setStatus(isOn: Boolean) { + setStatus(if (isOn) Status.ON else Status.OFF) + } + + enum class Status { + + ALWAYS_ON, + ON, + OFF, + ; + + companion object { + + fun from(value: Int): Status = when (value) { + 0 -> ALWAYS_ON + 1 -> ON + 2 -> OFF + else -> OFF + } + } + } +} diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt index b1dbff16a977..cd354bba2d9f 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt @@ -21,7 +21,8 @@ import android.util.AttributeSet import android.widget.ImageView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible -import com.duckduckgo.common.ui.view.StatusIndicator +import com.duckduckgo.common.ui.view.StatusIndicatorView +import com.duckduckgo.common.ui.view.StatusIndicatorView.Status import com.duckduckgo.common.ui.view.gone import com.duckduckgo.common.ui.view.show import com.duckduckgo.common.ui.view.text.DaxTextView @@ -43,7 +44,7 @@ class SettingsListItem @JvmOverloads constructor( get() = binding.leadingIcon val betaPill: ImageView get() = binding.betaPill - val statusIndicator: StatusIndicator + val statusIndicator: StatusIndicatorView get() = binding.statusIndicator init { @@ -66,8 +67,8 @@ class SettingsListItem @JvmOverloads constructor( setPillVisible(getBoolean(R.styleable.SettingsListItem_showBetaPill, false)) - val isOn = getBoolean(R.styleable.SettingsListItem_isOn, false) - statusIndicator.setStatus(isOn) + val indicatorStatus = Status.from(getInt(R.styleable.SettingsListItem_indicatorStatus, 0)) + statusIndicator.setStatus(indicatorStatus) recycle() } @@ -79,7 +80,7 @@ class SettingsListItem @JvmOverloads constructor( } fun setStatus(isOn: Boolean) { - statusIndicator.setStatus(isOn) + statusIndicator.setStatus(if (isOn) Status.ON else Status.OFF) } private fun setPillVisible(isVisible: Boolean) { diff --git a/common/common-ui/src/main/res/layout/component_settings.xml b/common/common-ui/src/main/res/layout/component_settings.xml index a22c55224d89..7c9820c82896 100644 --- a/common/common-ui/src/main/res/layout/component_settings.xml +++ b/common/common-ui/src/main/res/layout/component_settings.xml @@ -33,19 +33,35 @@ app:leadingIcon="@drawable/ic_dax_icon" app:primaryText="Settings List Item" /> + + + + @@ -54,7 +70,7 @@ android:id="@+id/settingsListItemWithBetaTagAndLongText" android:layout_width="match_parent" android:layout_height="wrap_content" - app:isOn="true" + app:indicatorStatus="on" app:leadingIcon="@drawable/ic_dax_icon" app:primaryText="Settings List Item with Beta Pill and a very long piece of text that should hopefully wrap" app:showBetaPill="true" /> diff --git a/common/common-ui/src/main/res/layout/view_settings_list_item.xml b/common/common-ui/src/main/res/layout/view_settings_list_item.xml index f31d09a85c5a..bde0d778b658 100644 --- a/common/common-ui/src/main/res/layout/view_settings_list_item.xml +++ b/common/common-ui/src/main/res/layout/view_settings_list_item.xml @@ -65,7 +65,7 @@ app:layout_constraintTop_toTopOf="@id/primaryText" tools:visibility="visible" /> - - + diff --git a/common/common-ui/src/main/res/values/attrs-settings-status-indicator.xml b/common/common-ui/src/main/res/values/attrs-settings-status-indicator.xml new file mode 100644 index 000000000000..8862a2987cf8 --- /dev/null +++ b/common/common-ui/src/main/res/values/attrs-settings-status-indicator.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/common/common-ui/src/main/res/values/strings-common-ui.xml b/common/common-ui/src/main/res/values/strings-common-ui.xml index 535b1e37ddda..cdd1a7cac200 100644 --- a/common/common-ui/src/main/res/values/strings-common-ui.xml +++ b/common/common-ui/src/main/res/values/strings-common-ui.xml @@ -16,7 +16,12 @@ - + Notify me + + + Always On + On + Off diff --git a/sync/sync-impl/build.gradle b/sync/sync-impl/build.gradle index b52258a0e2b3..ac389976085e 100644 --- a/sync/sync-impl/build.gradle +++ b/sync/sync-impl/build.gradle @@ -43,6 +43,7 @@ dependencies { implementation project(':navigation-api') implementation project(':remote-messaging-api') implementation project(path: ':autofill-api') + implementation project(path: ':settings-api') // temporary until we release new settings implementation project(path: ':app-build-config-api') implementation project(path: ':privacy-config-api') diff --git a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncActivity.kt b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncActivity.kt index 4289a04f4979..4c3b1452ec93 100644 --- a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncActivity.kt +++ b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/ui/SyncActivity.kt @@ -35,6 +35,7 @@ import com.duckduckgo.di.* import com.duckduckgo.di.scopes.* import com.duckduckgo.navigation.api.GlobalActivityStarter import com.duckduckgo.navigation.api.getActivityParams +import com.duckduckgo.settings.api.NewSettingsFeature import com.duckduckgo.sync.api.* import com.duckduckgo.sync.impl.ConnectedDevice import com.duckduckgo.sync.impl.PermissionRequest @@ -120,6 +121,9 @@ class SyncActivity : DuckDuckGoActivity() { @Inject lateinit var syncFeatureMessagesPlugin: DaggerSet + @Inject + lateinit var newSettingsFeature: NewSettingsFeature + private val syncIntroLauncher = registerForActivityResult( SyncIntroContract(), ) { resultOk -> @@ -148,6 +152,11 @@ class SyncActivity : DuckDuckGoActivity() { super.onCreate(savedInstanceState) setContentView(binding.root) setupToolbar(binding.includeToolbar.toolbar) + + if (newSettingsFeature.self().isEnabled()) { + binding.viewSyncDisabled.otherOptionsHeader.setText(R.string.sync_setup_other_options_title) + } + observeUiEvents() registerForPermission() configureSettings() diff --git a/sync/sync-impl/src/main/res/layout/view_sync_disabled.xml b/sync/sync-impl/src/main/res/layout/view_sync_disabled.xml index d39d01829b4b..f1d7caab2f39 100644 --- a/sync/sync-impl/src/main/res/layout/view_sync_disabled.xml +++ b/sync/sync-impl/src/main/res/layout/view_sync_disabled.xml @@ -50,9 +50,11 @@ @@ -71,6 +73,7 @@ - + Sync & Backup Unavailable\nSorry, but Sync & Backup is currently unavailable. Please try again later. Sync & Backup Unavailable\nSorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue. @@ -36,6 +36,7 @@ Securely sync bookmarks and passwords between your devices. Your data is end-to-end encrypted, and DuckDuckGo does not have access to the encryption key. Single-Device Setup + Other Options Recover Synced Data Sync and Back Up This Device