diff --git a/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx b/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx index 8b8eed6041d7..2e81c2440bc5 100644 --- a/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx +++ b/components/brave_new_tab_ui/components/default/braveNews/FeedV2.tsx @@ -11,8 +11,10 @@ import * as React from 'react' import styled from 'styled-components' import Feed from '../../../../brave_news/browser/resources/Feed' import FeedNavigation from '../../../../brave_news/browser/resources/FeedNavigation' +import NewsButton from '../../../../brave_news/browser/resources/NewsButton' import Variables from '../../../../brave_news/browser/resources/Variables' import { useBraveNews } from '../../../../brave_news/browser/resources/shared/Context' +import { isPublisherEnabled } from '../../../../brave_news/browser/resources/shared/api' import { CLASSNAME_PAGE_STUCK } from '../page' const Root = styled(Variables)` @@ -56,19 +58,13 @@ const ButtonsContainer = styled.div` background: var(--bn-glass-container); ` -const NewsButton = styled(Button)` +const SettingsButton = styled(Button)` --leo-button-color: var(--bn-glass-50); --leo-button-radius: ${radius.s}; --leo-button-padding: ${spacing.m}; ` -const LoadNewContentButton = styled(Button)` - --leo-button-color: var(--bn-glass-10); - - border-radius: 20px; - overflow: hidden; - backdrop-filter: brightness(0.8) blur(32px); - +const LoadNewContentButton = styled(NewsButton)` position: fixed; z-index: 1; top: ${spacing['3Xl']}; @@ -77,7 +73,22 @@ const LoadNewContentButton = styled(Button)` ` export default function FeedV2() { - const { feedV2, setCustomizePage, refreshFeedV2, feedV2UpdatesAvailable } = useBraveNews() + const { feedV2, setCustomizePage, refreshFeedV2, feedV2UpdatesAvailable, publishers, channels } = useBraveNews() + + // We don't want to decide whether we have subscriptions until the publishers + // and channels have loaded. + const loaded = React.useMemo(() => !!Object.values(publishers).length && !!Object.values(channels).length, [publishers, channels]) + + // This is a bit of an interesting |useMemo| - we only want it to be updated + // when the feed changes so as to not break the case where: + // 1. The user has no feeds (we show the NoFeeds card) + // 2. The user subscribes to a feed (we should still show the NoFeeds card, + // not the "Empty Feed") + // To achieve this, |hasSubscriptions| is only updated when the feed changes, + // or the opt-in status is changed. + const hasSubscriptions = React.useMemo(() => !loaded + || Object.values(publishers).some(isPublisherEnabled) + || Object.values(channels).some(c => c.subscribedLocales.length), [feedV2, loaded]) const ref = React.useRef() @@ -92,15 +103,6 @@ export default function FeedV2() { ref.current?.scrollIntoView() }, [feedV2?.items]) - // For some reason |createGlobalStyle| doesn't seem to work in Brave Core - // To get the background blur effect looking nice, we need to set the body - // background to black - unfortunately we can't do this in root HTML file - // because we want to avoid the background flash effect. - React.useEffect(() => { - // Note: This is always black because this doesn't support light mode. - document.body.style.backgroundColor = 'black'; - }, []) - return @@ -109,16 +111,16 @@ export default function FeedV2() { {feedV2UpdatesAvailable && {getLocale('braveNewsNewContentAvailable')} } - + - setCustomizePage('news')} title={getLocale('braveNewsCustomizeFeed')}> + setCustomizePage('news')} title={getLocale('braveNewsCustomizeFeed')}> - - { + + { refreshFeedV2() - }}> + }}> } diff --git a/components/brave_new_tab_ui/components/default/braveNews/cards/cardOptIn.tsx b/components/brave_new_tab_ui/components/default/braveNews/cards/cardOptIn.tsx index 2ddf51ceacba..43f664c8ab4a 100644 --- a/components/brave_new_tab_ui/components/default/braveNews/cards/cardOptIn.tsx +++ b/components/brave_new_tab_ui/components/default/braveNews/cards/cardOptIn.tsx @@ -8,6 +8,7 @@ import { getLocale, getLocaleWithTag } from '../../../../../common/locale' import * as Card from '../cardIntro' import BraveNewsLogoUrl from '../braveNewsLogo.svg' import { CardButton, TertiaryButton } from '../default' +import { NEWS_FEED_CLASS } from '../../../../../brave_news/browser/resources/Feed' type Props = { onOptIn: () => unknown @@ -19,7 +20,7 @@ const descriptionTwoTextParts = getLocaleWithTag('braveNewsIntroDescriptionTwo') export default function IntroCard (props: Props) { const introElementRef = React.useRef(null) return ( - + {getLocale('braveNewsIntroTitle')}
diff --git a/components/brave_news/browser/brave_news_controller.cc b/components/brave_news/browser/brave_news_controller.cc index e1d87d8acdc1..fb80dcab2de4 100644 --- a/components/brave_news/browser/brave_news_controller.cc +++ b/components/brave_news/browser/brave_news_controller.cc @@ -307,6 +307,12 @@ void BraveNewsController::SetChannelSubscribed( SetChannelSubscribedCallback callback) { auto result = channels_controller_.SetChannelSubscribed(locale, channel_id, subscribed); + + // When channels are changed, see if it affects the feed. + if (MaybeInitFeedV2()) { + feed_v2_builder_->RecheckFeedHash(); + } + std::move(callback).Run(std::move(result)); } diff --git a/components/brave_news/browser/channels_controller.cc b/components/brave_news/browser/channels_controller.cc index d60fe279ca9d..103a30f4f108 100644 --- a/components/brave_news/browser/channels_controller.cc +++ b/components/brave_news/browser/channels_controller.cc @@ -172,6 +172,11 @@ bool ChannelsController::GetChannelSubscribed(const std::string& locale, } void ChannelsController::OnPublishersUpdated(PublishersController* controller) { + // Don't notify updates until the pref is initialized. + if (!prefs_->HasPrefPath(prefs::kBraveNewsChannels)) { + return; + } + GetAllChannels(base::BindOnce( [](ChannelsController* controller, Channels channels) { auto event = mojom::ChannelsEvent::New(); diff --git a/components/brave_news/browser/feed_v2_builder.cc b/components/brave_news/browser/feed_v2_builder.cc index d5d1ee563ed3..bb55a3318637 100644 --- a/components/brave_news/browser/feed_v2_builder.cc +++ b/components/brave_news/browser/feed_v2_builder.cc @@ -85,9 +85,16 @@ std::string GetFeedHash(const Channels& channels, } for (const auto& [id, publisher] : publishers) { - if (publisher->user_enabled_status == mojom::UserEnabled::ENABLED) { + if (publisher->user_enabled_status == mojom::UserEnabled::ENABLED || + publisher->type == mojom::PublisherType::DIRECT_SOURCE) { hash_items.push_back(id); } + + // Disabling a publisher should also change the hash, as it will affect what + // articles can be shown. + if (publisher->user_enabled_status == mojom::UserEnabled::DISABLED) { + hash_items.push_back(id + "_disabled"); + } } for (const auto& [region, etag] : etags) { @@ -918,8 +925,7 @@ void FeedV2Builder::GetSignals(GetSignalsCallback callback) { weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } -void FeedV2Builder::OnPublishersUpdated( - PublishersController* publishers_controller) { +void FeedV2Builder::RecheckFeedHash() { const auto& publishers = publishers_controller_->GetLastPublishers(); auto channels = channels_controller_->GetChannelsFromPublishers(publishers, &*prefs_); @@ -929,6 +935,11 @@ void FeedV2Builder::OnPublishersUpdated( } } +void FeedV2Builder::OnPublishersUpdated( + PublishersController* publishers_controller) { + RecheckFeedHash(); +} + void FeedV2Builder::UpdateData(UpdateSettings settings, base::OnceCallback callback) { if (current_update_) { diff --git a/components/brave_news/browser/feed_v2_builder.h b/components/brave_news/browser/feed_v2_builder.h index c8e38a41fa37..f824866459e2 100644 --- a/components/brave_news/browser/feed_v2_builder.h +++ b/components/brave_news/browser/feed_v2_builder.h @@ -59,6 +59,8 @@ class FeedV2Builder : public PublishersController::Observer { void GetSignals(GetSignalsCallback callback); + void RecheckFeedHash(); + // PublishersController::Observer: void OnPublishersUpdated(PublishersController* controller) override; diff --git a/components/brave_news/browser/resources/Feed.tsx b/components/brave_news/browser/resources/Feed.tsx index 95650a8a4ac1..efe044332802 100644 --- a/components/brave_news/browser/resources/Feed.tsx +++ b/components/brave_news/browser/resources/Feed.tsx @@ -3,19 +3,20 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. +import { spacing } from "@brave/leo/tokens/css"; import { FeedItemV2, FeedV2 } from "gen/brave/components/brave_news/common/brave_news.mojom.m"; import * as React from 'react'; import styled from "styled-components"; import Advert from "./feed/Ad"; import Article from "./feed/Article"; +import CaughtUp from "./feed/CaughtUp"; import Cluster from "./feed/Cluster"; import Discover from "./feed/Discover"; import HeroArticle from "./feed/Hero"; -import { getHistoryValue, setHistoryState } from "./shared/history"; -import NoArticles from "./feed/NoArticles"; import LoadingCard from "./feed/LoadingCard"; -import { spacing } from "@brave/leo/tokens/css"; -import CaughtUp from "./feed/CaughtUp"; +import NoArticles from "./feed/NoArticles"; +import NoFeeds from "./feed/NoFeeds"; +import { getHistoryValue, setHistoryState } from "./shared/history"; // Restoring scroll position is complicated - we have two available strategies: // 1. Scroll to the same position - as long as the window hasn't been resized, @@ -44,6 +45,7 @@ const FeedContainer = styled.div` interface Props { feed: FeedV2 | undefined; + hasSubscriptions: boolean; } const getKey = (feedItem: FeedItemV2, index: number): React.Key => { @@ -74,7 +76,7 @@ const saveScrollPos = (itemId: React.Key) => () => { }) } -export default function Component({ feed }: Props) { +export default function Component({ feed, hasSubscriptions }: Props) { const [cardCount, setCardCount] = React.useState(getHistoryValue(HISTORY_CARD_COUNT, PAGE_SIZE)); // Store the number of cards we've loaded in history - otherwise when we @@ -144,12 +146,15 @@ export default function Component({ feed }: Props) { }, [cardCount, feed?.items]) return - {feed - ? !feed.items.length ? - : <> - {cards} - - - : } + {!hasSubscriptions + ? + : feed + ? !feed.items.length + ? + : <> + {cards} + + + : } } diff --git a/components/brave_news/browser/resources/FeedPage.tsx b/components/brave_news/browser/resources/FeedPage.tsx index 0a4881eeb779..5d50e6e22e30 100644 --- a/components/brave_news/browser/resources/FeedPage.tsx +++ b/components/brave_news/browser/resources/FeedPage.tsx @@ -23,6 +23,6 @@ export default function FeedPage() { const { feedV2 } = useBraveNews() return

The Feed ({feedV2?.items.length} items. Truncated at {truncate})

- +
} diff --git a/components/brave_news/browser/resources/NewsButton.tsx b/components/brave_news/browser/resources/NewsButton.tsx new file mode 100644 index 000000000000..3de580ae5f19 --- /dev/null +++ b/components/brave_news/browser/resources/NewsButton.tsx @@ -0,0 +1,14 @@ +// Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. +import Button from '@brave/leo/react/button' +import styled from 'styled-components' + +export default styled(Button)` + --leo-button-color: var(--bn-glass-10); + + border-radius: 20px; + overflow: hidden; + backdrop-filter: brightness(0.8) blur(32px); +` diff --git a/components/brave_news/browser/resources/Peek.tsx b/components/brave_news/browser/resources/Peek.tsx index 818fb181a91f..2a6b1695847f 100644 --- a/components/brave_news/browser/resources/Peek.tsx +++ b/components/brave_news/browser/resources/Peek.tsx @@ -98,26 +98,35 @@ const scrollToNews = () => { } export default function Peek() { - const { feedV2 } = useBraveNews() + const { feedV2, isShowOnNTPPrefEnabled, isOptInPrefEnabled } = useBraveNews() const top = feedV2?.items?.find(a => a.article || a.hero) const data = (top?.hero ?? top?.article)?.data const imageUrl = useUnpaddedImageUrl(data?.image.paddedImageUrl?.url ?? data?.image.imageUrl?.url, undefined, true) - if (!data) return null - - return - - - {getLocale('braveNewsNewsPeek')} - - - -
- - {data.title} -
- -
-
+ // For some reason |createGlobalStyle| doesn't seem to work in Brave Core + // To get the background blur effect looking nice, we need to set the body + // background to black - unfortunately we can't do this in root HTML file + // because we want to avoid the background flash effect. + React.useEffect(() => { + // Note: This is always black because this doesn't support light mode. + document.body.style.backgroundColor = 'black'; + }, []) + + return isShowOnNTPPrefEnabled + ? + {(!isOptInPrefEnabled || data) && + + {getLocale('braveNewsNewsPeek')} + + } + {data && +
+ + {data.title} +
+ +
} +
+ : null } diff --git a/components/brave_news/browser/resources/feed/NoArticles.tsx b/components/brave_news/browser/resources/feed/NoArticles.tsx index 499e5e222ed1..5e854ae89ade 100644 --- a/components/brave_news/browser/resources/feed/NoArticles.tsx +++ b/components/brave_news/browser/resources/feed/NoArticles.tsx @@ -36,24 +36,24 @@ export default function NoArticles() { - - + + - - + + - - + + - - + + - - + + diff --git a/components/brave_news/browser/resources/feed/NoFeeds.tsx b/components/brave_news/browser/resources/feed/NoFeeds.tsx new file mode 100644 index 000000000000..2bbbb2438d6b --- /dev/null +++ b/components/brave_news/browser/resources/feed/NoFeeds.tsx @@ -0,0 +1,45 @@ +// Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. +import Flex from '$web-common/Flex'; +import { getLocale } from '$web-common/locale'; +import { spacing } from '@brave/leo/tokens/css'; +import * as React from 'react'; +import styled from 'styled-components'; +import NewsButton from '../NewsButton'; +import { useBraveNews } from '../shared/Context'; +import { Title } from './Card'; + +const Container = styled(Flex)` + text-align: center; + + padding: ${spacing['3Xl']}; + gap: ${spacing.m}; + color: var(--bn-glass-70); + + & > svg { + margin-bottom: ${spacing.xl}; + fill: none; + } +` + +export default function NoFeeds() { + const { setCustomizePage } = useBraveNews() + return + + + + {getLocale('braveNewsNoContentHeading')} +
+ {getLocale('braveNewsNoContentMessage')} +
+ setCustomizePage('news')}> + {getLocale('braveNewsNoContentActionLabel')} + +
+} diff --git a/components/brave_news/browser/resources/shared/useFeedV2.ts b/components/brave_news/browser/resources/shared/useFeedV2.ts index 4e9fdaa41e0c..a0fe471acf4f 100644 --- a/components/brave_news/browser/resources/shared/useFeedV2.ts +++ b/components/brave_news/browser/resources/shared/useFeedV2.ts @@ -15,6 +15,7 @@ const MAX_AGE_FOR_LOCAL_STORAGE_FEED = 1000 * 60 * 60 const feedTypeToFeedView = (type: FeedV2Type | undefined): FeedView => { if (type?.channel) return `channels/${type.channel.channel}` if (type?.publisher) return `publishers/${type.publisher.publisherId}` + if (type?.following) return `following` return 'all' }