From fb9551295454f4e340113801c2e54fb296067dfe Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 13 May 2021 14:37:35 -0700 Subject: [PATCH 1/3] Brave News: require opt-in --- .storybook/locale.ts | 4 + browser/brave_profile_prefs.cc | 4 +- .../api/settings_private/brave_prefs_util.cc | 2 + browser/ui/webui/brave_webui_source.cc | 2 + .../brave_new_tab_message_handler.cc | 14 ++- .../chrome/browser/prefs/browser_prefs.cc | 2 + common/pref_names.cc | 1 + common/pref_names.h | 1 + .../brave_extension/background/today/index.ts | 76 +++++++----- .../brave_new_tab_ui/actions/today_actions.ts | 2 + .../brave_new_tab_ui/api/preferences.ts | 4 +- components/brave_new_tab_ui/async/today.ts | 5 + .../default/braveToday/cardIntro.ts | 38 ++++-- .../default/braveToday/cards/cardError.tsx | 4 +- .../default/braveToday/cards/cardIntro.tsx | 42 ------- .../default/braveToday/cards/cardOptIn.tsx | 41 +++++++ .../components/default/braveToday/default.ts | 64 +++++++++- .../components/default/braveToday/index.tsx | 114 +++++++++--------- .../default/braveToday/options/customize.tsx | 37 ++---- .../brave_new_tab_ui/containers/app.tsx | 5 - .../containers/newTab/index.tsx | 11 +- .../newTab/settings/braveToday/sources.tsx | 4 +- .../brave_new_tab_ui/reducers/today/index.ts | 3 + .../storage/new_tab_storage.ts | 2 +- .../stories/default/data/storybookState.ts | 2 +- .../default/data/todayStorybookState.ts | 6 +- .../brave_new_tab_ui/stories/regular.tsx | 5 - components/definitions/newTab.d.ts | 2 +- .../resources/brave_components_strings.grd | 8 +- 29 files changed, 301 insertions(+), 204 deletions(-) delete mode 100644 components/brave_new_tab_ui/components/default/braveToday/cards/cardIntro.tsx create mode 100644 components/brave_new_tab_ui/components/default/braveToday/cards/cardOptIn.tsx diff --git a/.storybook/locale.ts b/.storybook/locale.ts index 187fce5a6407..8d570ed4b149 100644 --- a/.storybook/locale.ts +++ b/.storybook/locale.ts @@ -348,6 +348,10 @@ let locale: Record = { rewardsWidgetEnableBrandedWallpaperTitle: 'Get paid to view this sponsored background image.', tosAndPp: 'By turning on {{title}}, you agree to the $1Terms of Service$2 and $3Privacy Policy$4.', braveTodayDisableSourceCommand: 'Disable content from $1', + braveTodayIntroTitle: `Today's top stories in a completely private feed, just for you.`, + braveTodayIntroDescription: `Brave News is ad-supported with completely private and anonymized ads matched on your device. Your personal information always stays private, per our $1privacy policy$2.`, + braveTodayOptInActionLabel: 'Show Brave News', + braveTodayOptOutActionLabel: 'No thanks' } export function provideStrings (strings: Record) { diff --git a/browser/brave_profile_prefs.cc b/browser/brave_profile_prefs.cc index 6f9711cdeb17..e18edd7d78d7 100644 --- a/browser/brave_profile_prefs.cc +++ b/browser/brave_profile_prefs.cc @@ -139,6 +139,8 @@ void RegisterProfilePrefsForMigration( registry->RegisterIntegerPref( kAlternativeSearchEngineProviderInTor, TemplateURLPrepopulateData::PREPOPULATED_ENGINE_ID_INVALID); + // Added 05/2021 + registry->RegisterBooleanPref(kBraveTodayIntroDismissed, false); } void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { @@ -304,7 +306,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { // Brave Today registry->RegisterDictionaryPref(kBraveTodaySources); - registry->RegisterBooleanPref(kBraveTodayIntroDismissed, false); + registry->RegisterBooleanPref(kBraveTodayOptedIn, false); registry->RegisterListPref(kBraveTodayWeeklySessionCount); registry->RegisterListPref(kBraveTodayWeeklyCardViewsCount); registry->RegisterListPref(kBraveTodayWeeklyCardVisitsCount); diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index 1b2b8f51bd97..c262f6014620 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -149,6 +149,8 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::PREF_TYPE_BOOLEAN; #endif // Brave today prefs + (*s_brave_allowlist)[kBraveTodayOptedIn] = + settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_brave_allowlist)[kBraveTodaySources] = settings_api::PrefType::PREF_TYPE_DICTIONARY; // Clear browsing data on exit prefs. diff --git a/browser/ui/webui/brave_webui_source.cc b/browser/ui/webui/brave_webui_source.cc index 22094b07ae68..a7b0403ecbe2 100644 --- a/browser/ui/webui/brave_webui_source.cc +++ b/browser/ui/webui/brave_webui_source.cc @@ -178,6 +178,8 @@ void CustomizeWebUIHTMLSource(const std::string &name, { "braveTodayTitle", IDS_BRAVE_TODAY_TITLE }, { "braveTodayIntroTitle", IDS_BRAVE_TODAY_INTRO_TITLE }, { "braveTodayIntroDescription", IDS_BRAVE_TODAY_INTRO_DESCRIPTION }, + { "braveTodayOptInActionLabel", IDS_BRAVE_TODAY_OPT_IN_ACTION_LABEL }, + { "braveTodayOptOutActionLabel", IDS_BRAVE_TODAY_OPT_OUT_ACTION_LABEL }, { "braveTodayStatusFetching", IDS_BRAVE_TODAY_STATUS_FETCHING}, { "braveTodayActionRefresh", IDS_BRAVE_TODAY_ACTION_REFRESH}, { "braveTodayScrollHint", IDS_BRAVE_TODAY_SCROLL_HINT}, diff --git a/browser/ui/webui/new_tab_page/brave_new_tab_message_handler.cc b/browser/ui/webui/new_tab_page/brave_new_tab_message_handler.cc index ef8889a201bb..3ec181692858 100644 --- a/browser/ui/webui/new_tab_page/brave_new_tab_message_handler.cc +++ b/browser/ui/webui/new_tab_page/brave_new_tab_message_handler.cc @@ -109,9 +109,8 @@ base::DictionaryValue GetPreferencesDictionary(PrefService* prefs) { pref_data.SetBoolean( "isBrandedWallpaperNotificationDismissed", prefs->GetBoolean(kBrandedWallpaperNotificationDismissed)); - pref_data.SetBoolean( - "isBraveTodayIntroDismissed", - prefs->GetBoolean(kBraveTodayIntroDismissed)); + pref_data.SetBoolean("isBraveTodayOptedIn", + prefs->GetBoolean(kBraveTodayOptedIn)); pref_data.SetBoolean( "showBinance", prefs->GetBoolean(kNewTabPageShowBinance)); @@ -323,6 +322,11 @@ void BraveNewTabMessageHandler::OnJavascriptAllowed() { base::Bind(&BraveNewTabMessageHandler::OnPrivatePropertiesChanged, base::Unretained(this))); } + // News + pref_change_registrar_.Add( + kBraveTodayOptedIn, + base::Bind(&BraveNewTabMessageHandler::OnPreferencesChanged, + base::Unretained(this))); // New Tab Page preferences pref_change_registrar_.Add(kNewTabPageShowBackgroundImage, base::Bind(&BraveNewTabMessageHandler::OnPreferencesChanged, @@ -470,8 +474,8 @@ void BraveNewTabMessageHandler::HandleSaveNewTabPagePref( settingsKey = kNewTabPageShowStats; } else if (settingsKeyInput == "showToday") { settingsKey = kNewTabPageShowToday; - } else if (settingsKeyInput == "isBraveTodayIntroDismissed") { - settingsKey = kBraveTodayIntroDismissed; + } else if (settingsKeyInput == "isBraveTodayOptedIn") { + settingsKey = kBraveTodayOptedIn; } else if (settingsKeyInput == "showRewards") { settingsKey = kNewTabPageShowRewards; } else if (settingsKeyInput == "isBrandedWallpaperNotificationDismissed") { diff --git a/chromium_src/chrome/browser/prefs/browser_prefs.cc b/chromium_src/chrome/browser/prefs/browser_prefs.cc index ad1086d1b036..dd88dcd5dc1b 100644 --- a/chromium_src/chrome/browser/prefs/browser_prefs.cc +++ b/chromium_src/chrome/browser/prefs/browser_prefs.cc @@ -60,6 +60,8 @@ void MigrateObsoleteProfilePrefs(Profile* profile) { // Added 04/2021 profile->GetPrefs()->ClearPref(kAlternativeSearchEngineProviderInTor); + // Added 05/2021 + profile->GetPrefs()->ClearPref(kBraveTodayIntroDismissed); } // This method should be periodically pruned of year+ old migrations. diff --git a/common/pref_names.cc b/common/pref_names.cc index d733e1787b0d..60a7bc3b27cd 100644 --- a/common/pref_names.cc +++ b/common/pref_names.cc @@ -62,6 +62,7 @@ const char kNewTabPageShowTogether[] = "brave.new_tab_page.show_together"; const char kNewTabPageShowsOptions[] = "brave.new_tab_page.shows_options"; const char kBraveTodaySources[] = "brave.today.sources"; const char kBraveTodayIntroDismissed[] = "brave.today.intro_dismissed"; +const char kBraveTodayOptedIn[] = "brave.today.opted_in"; const char kBraveTodayWeeklySessionCount[] = "brave.today.p3a_weekly_session_count"; const char kBraveTodayWeeklyCardViewsCount[] = diff --git a/common/pref_names.h b/common/pref_names.h index 98b1fbd1c979..ac466ac2d601 100644 --- a/common/pref_names.h +++ b/common/pref_names.h @@ -55,6 +55,7 @@ extern const char kNewTabPageShowTogether[]; extern const char kNewTabPageShowsOptions[]; extern const char kBraveTodaySources[]; extern const char kBraveTodayIntroDismissed[]; +extern const char kBraveTodayOptedIn[]; extern const char kBraveTodayWeeklySessionCount[]; extern const char kBraveTodayWeeklyCardViewsCount[]; extern const char kBraveTodayWeeklyCardVisitsCount[]; diff --git a/components/brave_extension/extension/brave_extension/background/today/index.ts b/components/brave_extension/extension/brave_extension/background/today/index.ts index 20347183a0c3..f145cded0d1b 100644 --- a/components/brave_extension/extension/brave_extension/background/today/index.ts +++ b/components/brave_extension/extension/brave_extension/background/today/index.ts @@ -4,15 +4,20 @@ // you can obtain one at http://mozilla.org/MPL/2.0/. import * as Background from '../../../../../common/Background' +import { getPreference } from '../../../../../common/settingsPrivate' import * as Feed from './feed' import * as Publishers from './publishers' import * as PublisherUserPrefs from './publisher-user-prefs' import { fetchResource, getUnpaddedAsDataUrl } from './privateCDN' const SETTINGS_KEY_SHOW_TODAY = 'brave.new_tab_page.show_brave_today' +const SETTINGS_KEY_OPTED_IN = 'brave.today.opted_in' const ALARM_KEY_FEED_UPDATE = 'brave-today-update-feed' const ALARM_KEY_PUBLISHERS_UPDATE = 'brave-today-update-publishers' +// Only do certain things when Brave Today is enabled +let isStartedUp = false + // // This module is the orchestrator for the Brave Today backend. // It handles command communication from front-ends, as well @@ -66,11 +71,6 @@ function stopUpdateFrequency () { chrome.alarms.clear(ALARM_KEY_PUBLISHERS_UPDATE) } -async function getHasBraveTodayBeenUsed () { - // We'll only have local data if data has been fetched or cached. - return !!(await Feed.getLocalData()) -} - // Setup listeners for messages from WebUI import MessageTypes = Background.MessageTypes.Today import Messages = BraveToday.Messages @@ -98,7 +98,7 @@ Background.setListener( // most NTP opens won't result in reading Brave Today // content (or changing publisher settings). // Only do this if we've previously interacted with brave today - if (await getHasBraveTodayBeenUsed()) { + if (isStartedUp) { await Promise.all([ Feed.getOrFetchData(), Publishers.getOrFetchData() @@ -110,6 +110,10 @@ Background.setListener( Background.setListener( MessageTypes.getFeed, async function (req, sender, sendResponse) { + if (!isStartedUp) { + sendResponse({ }) + return + } try { const feed = await Feed.getOrFetchData() // Only wait once. If there was an error or no data then return nothing. @@ -125,9 +129,12 @@ Background.setListener( Background.setListener( MessageTypes.getPublishers, async function (req, sender, sendResponse) { - // TODO: handle error - const publishers = await Publishers.getOrFetchData() + if (!isStartedUp) { + sendResponse({ publishers: {} }) + return + } // TODO(petemill): handle error + const publishers = await Publishers.getOrFetchData() sendResponse({ publishers }) } ) @@ -158,6 +165,10 @@ Background.setListener<{}, Messages.SetPublisherPrefPayload>( Background.setListener( MessageTypes.isFeedUpdateAvailable, async function (req, sender, sendResponse) { + if (!isStartedUp) { + sendResponse({ isUpdateAvailable: false }) + return + } const requestHash = req.hash // Check for update from local const feed = await Feed.getOrFetchData(true) @@ -171,24 +182,29 @@ Background.setListener( - MessageTypes.resetPrefsToDefault, - async function (req, sender, sendResponse) { - await PublisherUserPrefs.clearPrefs() - const publishers = await Publishers.update(true) - sendResponse({ publishers }) - }) - -// Only do certain things when Brave Today is enabled -let isStartedUp = false +Background.setListener< + Messages.ClearPrefsResponse, Messages.ClearPrefsPayload +>(MessageTypes.resetPrefsToDefault, async function (req, sender, sendResponse) { + await PublisherUserPrefs.clearPrefs() + const publishers = await Publishers.update(true) + sendResponse({ publishers }) +}) -async function conditionallyStartupOrShutdown (pref: chrome.settingsPrivate.PrefObject) { - if (pref.type !== chrome.settingsPrivate.PrefType.BOOLEAN) { - throw new Error(`Unknown pref type for '${SETTINGS_KEY_SHOW_TODAY}'. Expected BOOLEAN but saw ${pref.type}.`) +async function conditionallyStartupOrShutdown () { + const prefs = await Promise.all([ + getPreference(SETTINGS_KEY_SHOW_TODAY), + getPreference(SETTINGS_KEY_OPTED_IN) + ]) + let isBraveTodayEnabled = true + for (const pref of prefs) { + if (pref.type !== chrome.settingsPrivate.PrefType.BOOLEAN) { + throw new Error(`Unknown pref type for '${pref.key}'. Expected BOOLEAN but saw ${pref.type}.`) + } + if (!pref.value) { + isBraveTodayEnabled = false + } } - const isBraveTodayEnabled = pref.value - const hasBraveTodayBeenUsed = await getHasBraveTodayBeenUsed() - if (isBraveTodayEnabled && hasBraveTodayBeenUsed && !isStartedUp) { + if (isBraveTodayEnabled && !isStartedUp) { isStartedUp = true ensureUpdateFrequency() } else if (!isBraveTodayEnabled && isStartedUp) { @@ -197,11 +213,11 @@ async function conditionallyStartupOrShutdown (pref: chrome.settingsPrivate.Pref } } -chrome.settingsPrivate.getPref(SETTINGS_KEY_SHOW_TODAY, conditionallyStartupOrShutdown) - chrome.settingsPrivate.onPrefsChanged.addListener(async prefs => { - const pref = prefs.find(pref => pref.key === SETTINGS_KEY_SHOW_TODAY) - if (pref) { - await conditionallyStartupOrShutdown(pref) - } + await conditionallyStartupOrShutdown() +}) + +conditionallyStartupOrShutdown() +.catch((err) => { + console.error('Could not startup Brave News', err) }) diff --git a/components/brave_new_tab_ui/actions/today_actions.ts b/components/brave_new_tab_ui/actions/today_actions.ts index 6827657cd70f..b635394a43ab 100644 --- a/components/brave_new_tab_ui/actions/today_actions.ts +++ b/components/brave_new_tab_ui/actions/today_actions.ts @@ -17,6 +17,8 @@ type DataReceivedPayload = { } export const dataReceived = createAction('dataReceived') +export const optIn = createAction('optedIn') + /** * Scroll has reached a position so that another page of content is needed */ diff --git a/components/brave_new_tab_ui/api/preferences.ts b/components/brave_new_tab_ui/api/preferences.ts index 30dfe79fed34..8d463285f2f0 100644 --- a/components/brave_new_tab_ui/api/preferences.ts +++ b/components/brave_new_tab_ui/api/preferences.ts @@ -72,8 +72,8 @@ export function saveShowFTX (value: boolean): void { sendSavePref('showFTX', value) } -export function saveIsBraveTodayIntroDismissed (value: boolean): void { - sendSavePref('isBraveTodayIntroDismissed', value) +export function saveIsBraveTodayOptedIn (value: boolean): void { + sendSavePref('isBraveTodayOptedIn', value) } export function saveSetAllStackWidgets (value: boolean): void { diff --git a/components/brave_new_tab_ui/async/today.ts b/components/brave_new_tab_ui/async/today.ts index 32b45afe9c55..1b040c6fd71c 100644 --- a/components/brave_new_tab_ui/async/today.ts +++ b/components/brave_new_tab_ui/async/today.ts @@ -7,6 +7,7 @@ import AsyncActionHandler from '../../common/AsyncActionHandler' import * as Background from '../../common/Background' import * as Actions from '../actions/today_actions' import { ApplicationState } from '../reducers' +import { saveIsBraveTodayOptedIn } from '../api/preferences' function storeInHistoryState (data: Object) { const oldHistoryState = (typeof history.state === 'object') ? history.state : {} @@ -45,6 +46,10 @@ handler.on( } ) +handler.on(Actions.optIn.getType(), async () => { + saveIsBraveTodayOptedIn(true) +}) + handler.on(Actions.ensureSettingsData.getType(), async (store) => { const state = store.getState() as ApplicationState if (state.today.publishers && Object.keys(state.today.publishers).length) { diff --git a/components/brave_new_tab_ui/components/default/braveToday/cardIntro.ts b/components/brave_new_tab_ui/components/default/braveToday/cardIntro.ts index 2d368b3db4e7..cf16b790dc32 100644 --- a/components/brave_new_tab_ui/components/default/braveToday/cardIntro.ts +++ b/components/brave_new_tab_ui/components/default/braveToday/cardIntro.ts @@ -6,28 +6,40 @@ import styled from 'styled-components' import { Block as StandardBlock, - Heading as StandardHeading, - Image as StandardImage, - Text as StandardText + Image as StandardImage } from './default' export const Intro = styled(StandardBlock)` + font-family: Poppins; + color: white; text-align: center; padding: 44px 90px 36px; + display: flex; + flex-direction: column; + gap: 18px; + align-items: center; ` -export const Image = styled(StandardImage)` - margin: auto; - height: 60px; +export const Title = styled('h2')` + margin: 0; + font-weight: 600; + font-size: 28px; + line-height: 38px; ` -export const Heading = styled(StandardHeading)` - margin: 36px 0 24px; +export const Paragraph = styled('p')` + margin: 0; + font-weight: 500; + font-size: 14px; + line-height: 17px; + letter-spacing: 0.16px; + a { + color: inherit; + text-decoration: underline; + } ` -export const Text = styled(StandardText)` - ${Intro} & { - font-weight: 500; - line-height: 20px; - } +export const Image = styled(StandardImage)` + margin: auto; + height: 60px; ` diff --git a/components/brave_new_tab_ui/components/default/braveToday/cards/cardError.tsx b/components/brave_new_tab_ui/components/default/braveToday/cards/cardError.tsx index 6d0996aa4d1a..f25b88815260 100644 --- a/components/brave_new_tab_ui/components/default/braveToday/cards/cardError.tsx +++ b/components/brave_new_tab_ui/components/default/braveToday/cards/cardError.tsx @@ -48,9 +48,9 @@ export default function CardError () { Oops… - + Brave Today is experiencing some issues. Try again. - + ) diff --git a/components/brave_new_tab_ui/components/default/braveToday/cards/cardIntro.tsx b/components/brave_new_tab_ui/components/default/braveToday/cards/cardIntro.tsx deleted file mode 100644 index 270a176a9d7f..000000000000 --- a/components/brave_new_tab_ui/components/default/braveToday/cards/cardIntro.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2020 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// you can obtain one at http://mozilla.org/MPL/2.0/. - -import * as React from 'react' -import { getLocale } from '../../../../../common/locale' -import VisibilityTimer from '../../../../helpers/visibilityTimer' -import * as Card from '../cardIntro' -import BraveTodayLogoUrl from '../braveTodayLogo.svg' - -const timeToHideMs = 4000 - -type Props = { - onRead: () => void -} - -export default function IntroCard (props: Props) { - const introElementRef = React.useRef(null) - - // Only mark as 'read' when it's been in the viewport for a - // specific amount of time, and the tab is active. - React.useEffect(() => { - const element = introElementRef.current - if (!element) { - return - } - const observer = new VisibilityTimer(props.onRead, timeToHideMs, element) - observer.startTracking() - return () => { - observer.stopTracking() - } - }, [introElementRef.current, props.onRead]) - - return ( - - - {getLocale('braveTodayIntroTitle')} - {getLocale('braveTodayIntroDescription')} - - ) -} diff --git a/components/brave_new_tab_ui/components/default/braveToday/cards/cardOptIn.tsx b/components/brave_new_tab_ui/components/default/braveToday/cards/cardOptIn.tsx new file mode 100644 index 000000000000..5f1bd22b028d --- /dev/null +++ b/components/brave_new_tab_ui/components/default/braveToday/cards/cardOptIn.tsx @@ -0,0 +1,41 @@ +// Copyright (c) 2020 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { getLocale, getLocaleWithTag } from '../../../../../common/locale' +import * as Card from '../cardIntro' +import BraveTodayLogoUrl from '../braveTodayLogo.svg' +import { CardButton, TertiaryButton } from '../default' + +type Props = { + onOptIn: () => unknown + onDisable: () => unknown +} + +export default function IntroCard (props: Props) { + const introElementRef = React.useRef(null) + const descriptionTextParts = React.useMemo(() => { + return getLocaleWithTag('braveTodayIntroDescription') + }, []) + return ( + + + {getLocale('braveTodayIntroTitle')} + + {descriptionTextParts.beforeTag} + + {descriptionTextParts.duringTag} + + {descriptionTextParts.afterTag} + + + {getLocale('braveTodayOptInActionLabel')} + + + {getLocale('braveTodayOptOutActionLabel')} + + + ) +} diff --git a/components/brave_new_tab_ui/components/default/braveToday/default.ts b/components/brave_new_tab_ui/components/default/braveToday/default.ts index 25b256951f2d..b5a6c37c71bc 100644 --- a/components/brave_new_tab_ui/components/default/braveToday/default.ts +++ b/components/brave_new_tab_ui/components/default/braveToday/default.ts @@ -3,7 +3,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // you can obtain one at http://mozilla.org/MPL/2.0/. -import styled, { keyframes } from 'styled-components' +import styled, { css, keyframes } from 'styled-components' const blurOut = keyframes` from { @@ -103,3 +103,65 @@ export const PublisherLogo = styled('img')<{}>` margin-top: 12px; display: inline-block; ` + +export const Button = styled('button')<{}>` + appearance: none; + cursor: pointer; + display: block; + border-radius: 24px; + background: none; + padding: 15px 34px; + color: white; + border: none; + font-weight: 800; + cursor: pointer; + background: rgba(33, 37, 41, .8); + backdrop-filter: blur(8px); + outline: none; + border: none; + transition: opacity 1s ease-in-out, background .124s ease-in-out; + &:hover { + background: rgba(255, 255, 255, .2); + } + &:active { + background: rgba(255, 255, 255, .4); + } + &:focus-visible { + box-shadow: 0 0 0 1px ${p => p.theme.color.brandBrave}; + } +` + +type CardButtonProps = { + isMainFocus?: boolean +} +export const CardButton = styled(Button)` + backdrop-filter: none; + background: rgba(255, 255, 255, .24); + ${p => p.isMainFocus && css` + display: block; + width: 100%; + `} +` + +export const TertiaryButton = styled('button')<{}>` + appearance: none; + cursor: pointer; + font-family: Poppins; + font-weight: 600; + font-size: 13px; + background: none; + border: none; + outline: none; + margin: 0; + padding: 0; + color: white; + &:hover { + color: #ddd; + } + &:active { + transform: translate(1px, 1px) + } + &:focus-visible { + box-shadow: 0 0 0 1px ${p => p.theme.color.brandBrave}; + } +` diff --git a/components/brave_new_tab_ui/components/default/braveToday/index.tsx b/components/brave_new_tab_ui/components/default/braveToday/index.tsx index 2e17c22f4789..3525072b0c67 100644 --- a/components/brave_new_tab_ui/components/default/braveToday/index.tsx +++ b/components/brave_new_tab_ui/components/default/braveToday/index.tsx @@ -5,29 +5,25 @@ import * as React from 'react' import * as BraveTodayElement from './default' -import CardIntro from './cards/cardIntro' +import CardOptIn from './cards/cardOptIn' import CardLoading from './cards/cardLoading' import { ReadFeedItemPayload } from '../../../actions/today_actions' const Content = React.lazy(() => import('./content')) -type State = { - hasInteractionStarted: boolean - isIntroCardVisible: boolean -} - export type OnReadFeedItem = (args: ReadFeedItemPayload) => any export type OnSetPublisherPref = (publisherId: string, enabled: boolean) => any export type OnPromotedItemViewed = (item: BraveToday.FeedItem) => any export type Props = { isFetching: boolean + hasInteracted: boolean isUpdateAvailable: boolean - isIntroDismissed: boolean + isOptedIn: boolean feed?: BraveToday.Feed publishers?: BraveToday.Publishers articleToScrollTo?: BraveToday.FeedItem displayedPageCount: number - onInteracting: (interacting: boolean) => any + onInteracting: () => any onReadFeedItem: OnReadFeedItem onPromotedItemViewed: OnPromotedItemViewed onFeedItemViewedCountChanged: (feedItemsViewed: number) => any @@ -36,65 +32,71 @@ export type Props = { onCustomizeBraveToday: () => any onRefresh: () => any onCheckForUpdate: () => any - onReadCardIntro: () => any + onOptIn: () => any + onDisable: () => unknown } export const attributeNameCardCount = 'data-today-card-count' -class BraveToday extends React.PureComponent { - braveTodayHitsViewportObserver: IntersectionObserver - scrollTriggerToFocusBraveToday: any // React.RefObject +const intersectionOptions = { root: null, rootMargin: '0px', threshold: 0.25 } - constructor (props: Props) { - super(props) - // Don't remove Intro Card until the page refreshes - this.state = { - hasInteractionStarted: false, - isIntroCardVisible: !props.isIntroDismissed +export default function BraveTodayContent (props: Props) { + const handleHitsViewportObserver = React.useCallback((entries) => { + // When the scroll trigger, hits the viewport, notify externally, and since + // we won't get updated with that result, change our internal state. + const isIntersecting = entries.some(entry => entry.isIntersecting) + if (isIntersecting) { + props.onInteracting() } - } - - componentDidMount () { - const options = { root: null, rootMargin: '0px', threshold: 0.25 } + }, [props.onInteracting]) - this.braveTodayHitsViewportObserver = new - IntersectionObserver(this.handleBraveTodayHitsViewportObserver, options) + const viewportObserver = React.useRef() + React.useEffect(() => { + // Setup intersection observer params + console.log('setting today viewport observer, should only happen once') + viewportObserver.current = new IntersectionObserver(handleHitsViewportObserver, intersectionOptions) + }, [ handleHitsViewportObserver ]) - // Handle first card showing up so we can hide secondary UI - this.braveTodayHitsViewportObserver.observe(this.scrollTriggerToFocusBraveToday) - } + const scrollTrigger = React.useRef(null) - handleBraveTodayHitsViewportObserver = (entries: IntersectionObserverEntry[]) => { - const isIntersecting = entries.some(entry => entry.isIntersecting) - this.props.onInteracting(isIntersecting) - if (isIntersecting) { - this.setState({ hasInteractionStarted: true }) + React.useEffect(() => { + // When we have an element to observe, set it as the target + const observer = viewportObserver.current + // Don't do anything if we're still setting up, or if we've already + // observed and set `hasInteracted`. + if (!observer || !scrollTrigger.current || !props.isOptedIn || props.hasInteracted) { + return } - } + observer.observe(scrollTrigger.current) + return () => { + // Cleanup current observer if we get a new observer, or a new element to observe + observer.disconnect() + } + }, [ scrollTrigger.current, viewportObserver.current, props.isOptedIn, props.hasInteracted ]) - render () { - const shouldDisplayContent = - this.state.hasInteractionStarted || - !!this.props.articleToScrollTo + // Only load all the content DOM elements if we're + // scrolled far down enough, otherwise it's too easy to scroll down + // by accident and get all the elements added. + // Also sanity check isOptedIn, but without it there shouldn't be any content + // anyway. + const shouldDisplayContent = props.isOptedIn && + (props.hasInteracted || !!props.articleToScrollTo) - return ( - -
(this.scrollTriggerToFocusBraveToday = scrollTrigger)} - style={{ position: 'sticky', top: '100px' }} - /> - { !this.props.isIntroDismissed && - - } - { shouldDisplayContent && - )}> - - - } + return ( + +
+ { !props.isOptedIn && + + } + { shouldDisplayContent && + )}> + + + } - - ) - } + + ) } - -export default BraveToday diff --git a/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx b/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx index 0b7ef1d74327..6df8551bd207 100644 --- a/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx +++ b/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx @@ -6,48 +6,27 @@ import * as React from 'react' import styled from 'styled-components' import { getLocale } from '../../../../../common/locale' +import { Button } from '../default' type Props = { show: boolean onCustomizeBraveToday: () => any } -const CustomizeButton = styled('button')` +const Hideable = styled('div')` position: fixed; right: 20px; bottom: 20px; - appearance: none; - cursor: pointer; - display: block; - border-radius: 24px; - background: none; - backdrop-filter: blur(25px); - padding: 15px 34px; - color: white; - border: none; - font-weight: 800; - cursor: pointer; opacity: ${p => p.show ? 1 : 0}; - background: rgba(33, 37, 41, .8); - backdrop-filter: blur(8px); - transition: opacity 1s ease-in-out, background .124s ease-in-out; - outline: none; - border: none; - &:hover { - background: rgba(255, 255, 255, .2); - } - &:active { - background: rgba(255, 255, 255, .4); - } - &:focus-visible { - box-shadow: 0 0 0 1px ${p => p.theme.color.brandBrave}; - } + transition: opacity 1s ease-in-out; ` export default function Customize (props: Props) { return ( - - {getLocale('customize')} - + + + ) } diff --git a/components/brave_new_tab_ui/containers/app.tsx b/components/brave_new_tab_ui/containers/app.tsx index 1e4fbeb17063..6c0e1d9ed873 100644 --- a/components/brave_new_tab_ui/containers/app.tsx +++ b/components/brave_new_tab_ui/containers/app.tsx @@ -32,10 +32,6 @@ interface Props { ftx: FTXState } -function dismissBraveTodayIntroCard () { - PreferencesAPI.saveIsBraveTodayIntroDismissed(true) -} - function DefaultPage (props: Props) { const { newTabData, braveTodayData, gridSitesData, actions } = props @@ -63,7 +59,6 @@ function DefaultPage (props: Props) { saveShowCryptoDotCom={PreferencesAPI.saveShowCryptoDotCom} saveShowFTX={PreferencesAPI.saveShowFTX} saveBrandedWallpaperOptIn={PreferencesAPI.saveBrandedWallpaperOptIn} - onReadBraveTodayIntroCard={dismissBraveTodayIntroCard} saveSetAllStackWidgets={PreferencesAPI.saveSetAllStackWidgets} /> ) diff --git a/components/brave_new_tab_ui/containers/newTab/index.tsx b/components/brave_new_tab_ui/containers/newTab/index.tsx index e25cf2bcac7e..9a3f2e1608e8 100644 --- a/components/brave_new_tab_ui/containers/newTab/index.tsx +++ b/components/brave_new_tab_ui/containers/newTab/index.tsx @@ -67,7 +67,6 @@ interface Props { saveShowCryptoDotCom: (value: boolean) => void saveShowFTX: (value: boolean) => void saveBrandedWallpaperOptIn: (value: boolean) => void - onReadBraveTodayIntroCard: () => any saveSetAllStackWidgets: (value: boolean) => void } @@ -473,8 +472,8 @@ class NewTabPage extends React.Component { this.props.actions.setUserTLDAutoSet() } - onBraveTodayInteracting = (isInteracting: boolean) => { - if (isInteracting && !this.hasInitBraveToday) { + onBraveTodayInteracting = () => { + if (!this.hasInitBraveToday) { this.hasInitBraveToday = true this.props.actions.today.interactionBegin() } @@ -1202,11 +1201,13 @@ class NewTabPage extends React.Component { displayedPageCount={this.props.todayData.currentPageIndex} publishers={this.props.todayData.publishers} isFetching={this.props.todayData.isFetching === true} + hasInteracted={this.props.todayData.hasInteracted} isUpdateAvailable={this.props.todayData.isUpdateAvailable} - isIntroDismissed={this.props.newTabData.isBraveTodayIntroDismissed} + isOptedIn={this.props.newTabData.isBraveTodayOptedIn} onRefresh={this.props.actions.today.refresh} onAnotherPageNeeded={this.props.actions.today.anotherPageNeeded} onInteracting={this.onBraveTodayInteracting} + onDisable={this.toggleShowToday} onFeedItemViewedCountChanged={this.props.actions.today.feedItemViewedCountChanged} // tslint:disable-next-line:jsx-no-lambda onCustomizeBraveToday={() => { this.openSettings(SettingsTabType.BraveToday) }} @@ -1214,7 +1215,7 @@ class NewTabPage extends React.Component { onPromotedItemViewed={this.props.actions.today.promotedItemViewed} onSetPublisherPref={this.props.actions.today.setPublisherPref} onCheckForUpdate={this.props.actions.today.checkForUpdate} - onReadCardIntro={this.props.onReadBraveTodayIntroCard} + onOptIn={this.props.actions.today.optIn} /> } { props.setCategory('') }, [props.setCategory]) - // No publishers + // No publishers, could be because hasn't opted-in yet // TODO(petemill): error state if (!props.publishers) { - return
Loading...
+ return null } // Category list if (!props.category) { diff --git a/components/brave_new_tab_ui/reducers/today/index.ts b/components/brave_new_tab_ui/reducers/today/index.ts index 5e943ac13d75..fb156837b0e8 100644 --- a/components/brave_new_tab_ui/reducers/today/index.ts +++ b/components/brave_new_tab_ui/reducers/today/index.ts @@ -10,6 +10,7 @@ export type BraveTodayState = { // Are we in the middle of checking for new data isFetching: boolean | string isUpdateAvailable: boolean + hasInteracted: boolean // How many pages have been displayed so far for the current data currentPageIndex: number cardsViewed: number @@ -29,6 +30,7 @@ function storeInHistoryState (data: Object) { const defaultState: BraveTodayState = { isFetching: true, isUpdateAvailable: false, + hasInteracted: false, currentPageIndex: 0, cardsViewed: 0, cardsVisited: 0 @@ -58,6 +60,7 @@ export default reducer reducer.on(Actions.interactionBegin, (state, payload) => ({ ...state, + hasInteracted: true, isFetching: true })) diff --git a/components/brave_new_tab_ui/storage/new_tab_storage.ts b/components/brave_new_tab_ui/storage/new_tab_storage.ts index ed2ea93f44cf..f28c36586a5b 100644 --- a/components/brave_new_tab_ui/storage/new_tab_storage.ts +++ b/components/brave_new_tab_ui/storage/new_tab_storage.ts @@ -29,7 +29,7 @@ export const defaultState: NewTab.State = { showFTX: false, brandedWallpaperOptIn: false, isBrandedWallpaperNotificationDismissed: true, - isBraveTodayIntroDismissed: false, + isBraveTodayOptedIn: false, showEmptyPage: false, togetherSupported: false, geminiSupported: false, diff --git a/components/brave_new_tab_ui/stories/default/data/storybookState.ts b/components/brave_new_tab_ui/stories/default/data/storybookState.ts index 0578d3b304df..52028081be4e 100644 --- a/components/brave_new_tab_ui/stories/default/data/storybookState.ts +++ b/components/brave_new_tab_ui/stories/default/data/storybookState.ts @@ -81,7 +81,7 @@ export const getNewTabData = (state: NewTab.State = defaultState): NewTab.State ftxSupported: boolean('FTX supported?', true), showFTX: boolean('Show FTX?', true), showBinance: boolean('Show Binance?', true), - isBraveTodayIntroDismissed: boolean('Brave Today intro dismissed?', false), + isBraveTodayOptedIn: boolean('Brave Today opted-in?', false), textDirection: select('Text direction', { ltr: 'ltr', rtl: 'rtl' } , 'ltr'), stats: { ...state.stats, diff --git a/components/brave_new_tab_ui/stories/default/data/todayStorybookState.ts b/components/brave_new_tab_ui/stories/default/data/todayStorybookState.ts index e6fbab4cf31d..cb32f7b14b30 100644 --- a/components/brave_new_tab_ui/stories/default/data/todayStorybookState.ts +++ b/components/brave_new_tab_ui/stories/default/data/todayStorybookState.ts @@ -2,13 +2,15 @@ import { boolean } from '@storybook/addon-knobs' import { BraveTodayState } from '../../../reducers/today' export default function getTodayState (): BraveTodayState { + const hasDataError = boolean('Today data fetch error?', false) return { isFetching: boolean('Today is fetching?', false), + hasInteracted: boolean('Today has interacted?', true), isUpdateAvailable: boolean('Is Today update available?', false), currentPageIndex: 10, cardsViewed: 0, cardsVisited: 0, - publishers: { + publishers: hasDataError ? undefined : { ['5eece347713f329f156cd0204cf9b12629f1dc8f4ea3c1b67984cfbfd66cdca5']: { publisher_id: `5eece347713f329f156cd0204cf9b12629f1dc8f4ea3c1b67984cfbfd66cdca5`, publisher_name: `Test Publisher 1`, @@ -73,7 +75,7 @@ export default function getTodayState (): BraveTodayState { user_enabled: null } }, - feed: { + feed: hasDataError ? undefined : { hash: `123abc`, featuredArticle: { category: `Top News`, diff --git a/components/brave_new_tab_ui/stories/regular.tsx b/components/brave_new_tab_ui/stories/regular.tsx index f55b881616e6..9d028f2c6123 100644 --- a/components/brave_new_tab_ui/stories/regular.tsx +++ b/components/brave_new_tab_ui/stories/regular.tsx @@ -40,10 +40,6 @@ const StoreProvider: React.FunctionComponent = ({ children }) => { ) } -function dismissBraveTodayIntroCard () { - console.log('brave today intro card dismissed') -} - export default { title: 'New Tab', decorators: [ @@ -75,7 +71,6 @@ export const Regular = () => { saveShowFTX={doNothing} saveBrandedWallpaperOptIn={doNothing} saveSetAllStackWidgets={doNothing} - onReadBraveTodayIntroCard={dismissBraveTodayIntroCard} /> ) } diff --git a/components/definitions/newTab.d.ts b/components/definitions/newTab.d.ts index 6eabfaef4877..39a1d2268dd8 100644 --- a/components/definitions/newTab.d.ts +++ b/components/definitions/newTab.d.ts @@ -105,7 +105,7 @@ declare namespace NewTab { clockFormat: string showTopSites: boolean showRewards: boolean - isBraveTodayIntroDismissed: boolean + isBraveTodayOptedIn: boolean isBrandedWallpaperNotificationDismissed: boolean } diff --git a/components/resources/brave_components_strings.grd b/components/resources/brave_components_strings.grd index 5d66206725c3..1c1d79db3573 100644 --- a/components/resources/brave_components_strings.grd +++ b/components/resources/brave_components_strings.grd @@ -250,7 +250,13 @@ Today's top stories in a completely private feed, just for you. - Brave Today matches your interests on your device so your personal information never leaves your browser. New content updated throughout the day. + Brave Today is ad-supported with completely private and anonymized ads matched on your device. Your personal information always stays private, per our $1{privacy policy$2}. + + + Show Brave Today + + + No thanks Fetching… From 29b952181781ed4f65b10f7900997c68475358cc Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Mon, 17 May 2021 14:52:43 -0700 Subject: [PATCH 2/3] Display name for Brave Today is now Brave News --- .../newTab/settings/braveToday/index.tsx | 4 +-- .../resources/brave_components_strings.grd | 26 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/brave_new_tab_ui/containers/newTab/settings/braveToday/index.tsx b/components/brave_new_tab_ui/containers/newTab/settings/braveToday/index.tsx index 52b993dc92da..01c3a58293c2 100644 --- a/components/brave_new_tab_ui/containers/newTab/settings/braveToday/index.tsx +++ b/components/brave_new_tab_ui/containers/newTab/settings/braveToday/index.tsx @@ -45,7 +45,7 @@ export default function BraveTodayPrefs (props: Props) { {!category && ( - Show Brave Today + {getLocale('braveTodayOptInActionLabel')} )} - {props.showToday && + {props.showToday && props.publishers && Object.keys(props.publishers).length && } {!category && ( diff --git a/components/resources/brave_components_strings.grd b/components/resources/brave_components_strings.grd index 1c1d79db3573..df88685da48c 100644 --- a/components/resources/brave_components_strings.grd +++ b/components/resources/brave_components_strings.grd @@ -243,17 +243,17 @@ Try it out - - Brave Today + + Brave News - + Today's top stories in a completely private feed, just for you. - - Brave Today is ad-supported with completely private and anonymized ads matched on your device. Your personal information always stays private, per our $1{privacy policy$2}. + + Brave News is ad-supported with completely private and anonymized ads matched on your device. Your personal information always stays private, per our $1{privacy policy$2}. - Show Brave Today + Show Brave News No thanks @@ -264,19 +264,19 @@ Load new content - - Scroll for Brave Today + + Scroll for Brave News - - Reset Brave Today sources + + Reset Brave News sources - - Reset all your Brave Today publisher choices to their default? + + Reset all your Brave News publisher choices to their default? All sources - + Sources From eef0de690ae406c4ee406ab5c393d7482222debb Mon Sep 17 00:00:00 2001 From: Evgeny Klimenchenko Date: Sun, 11 Apr 2021 14:36:52 +0100 Subject: [PATCH 3/3] Fix customise button preventing to click setting icon When button is not visible it won't have any even listeners and won't preven tother buttons to be clicked. # Conflicts: # components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx --- .../components/default/braveToday/options/customize.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx b/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx index 6df8551bd207..6ff17848330a 100644 --- a/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx +++ b/components/brave_new_tab_ui/components/default/braveToday/options/customize.tsx @@ -14,6 +14,7 @@ type Props = { } const Hideable = styled('div')` + pointer-events: ${p => p.show ? 'auto' : 'none'}; position: fixed; right: 20px; bottom: 20px; @@ -24,7 +25,11 @@ const Hideable = styled('div')` export default function Customize (props: Props) { return ( -