feat(mobile): implement language-based preset feeds for onboarding (#3109)

* feat(mobile): implement language-based preset feeds for onboarding

* fix: enhance useIsNewUser hook to accept enabled parameter

* refactor(mobile): streamline subscription handling in StepInterests component

* fix(mobile): correct feedId values in English feeds JSON
This commit is contained in:
Whitewater 2025-03-15 00:40:55 +08:00 committed by GitHub
parent ca897ebf2c
commit 9f07a3b0fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1755 additions and 141 deletions

View File

@ -0,0 +1,263 @@
[
{
"feedId": "43255084704601095",
"title": "a16z Podcast",
"url": "https://feeds.simplecast.com/JGE3yC0V",
"language": "English"
},
{
"feedId": "83007794771225600",
"title": "Andrew Huberman - YouTube",
"url": "rsshub://youtube/user/@hubermanlab",
"language": "English",
"view": 3
},
{
"feedId": "62128288597853189",
"title": "Cal Newport",
"url": "https://calnewport.com/feed/",
"language": "English"
},
{
"feedId": "43881675446885376",
"title": "Collab Fund",
"url": "http://feeds.feedburner.com/collabfund",
"language": "English"
},
{
"feedId": "41359648684677175",
"title": "Derek Sivers blog",
"url": "https://sive.rs/en.atom",
"language": "English"
},
{
"feedId": "52325519371718656",
"title": "Hacker News",
"url": "rsshub://hackernews",
"language": "English"
},
{
"feedId": "41381007770949636",
"title": "IGN Articles",
"url": "https://www.ign.com/rss/articles/feed?tags=games",
"language": "English"
},
{
"feedId": "59461847827447808",
"title": "International homepage",
"url": "https://www.ft.com/rss/home",
"language": "English"
},
{
"feedId": "52333046926856207",
"title": "Kotaku",
"url": "https://kotaku.com/rss",
"language": "English"
},
{
"feedId": "41768239731545104",
"title": "LessWrong",
"url": "https://www.lesswrong.com/feed.xml?view=curated-rss",
"language": "English"
},
{
"feedId": "41375850878492672",
"title": "Lex Fridman - YouTube",
"url": "rsshub://youtube/user/%40lexfridman",
"language": "English",
"view": 3
},
{
"feedId": "55258726035912721",
"title": "Marginal REVOLUTION",
"url": "https://marginalrevolution.com/feed",
"language": "English"
},
{
"feedId": "42109057149046784",
"title": "Marques Brownlee - YouTube",
"url": "rsshub://youtube/user/@mkbhd",
"language": "English",
"view": 3
},
{
"feedId": "41768239731545089",
"title": "MIT Technology Review",
"url": "https://www.technologyreview.com/feed/",
"language": "English"
},
{
"feedId": "42127302309630072",
"title": "Mr. Money Mustache",
"url": "https://feeds.feedburner.com/mrmoneymustache",
"language": "English"
},
{
"feedId": "41356263889737728",
"title": "NASA Astronomy Picture of the Day",
"url": "rsshub://nasa/apod",
"language": "English"
},
{
"feedId": "55130722692595739",
"title": "Nassim Taleb",
"url": "https://nassimtaleb.org/feed/",
"language": "English"
},
{
"feedId": "41699925856588800",
"title": "Nat Geo Photo of the Day",
"url": "rsshub://natgeo/dailyphoto",
"language": "English"
},
{
"feedId": "42127302309630013",
"title": "Nautilus",
"url": "https://nautil.us/feed/",
"language": "English"
},
{
"feedId": "41147805276726370",
"title": "Obsidian Changelog",
"url": "https://obsidian.md/changelog.xml",
"language": "English"
},
{
"feedId": "41468521403732992",
"title": "Paul Graham - Essays",
"url": "rsshub://paulgraham/articles",
"language": "English"
},
{
"feedId": "55130722692595736",
"title": "Peter Attia",
"url": "https://peterattiamd.com/feed/",
"language": "English"
},
{
"feedId": "41382261184290816",
"title": "Product Hunt — The best new products, every day",
"url": "https://www.producthunt.com/feed",
"language": "English"
},
{
"feedId": "43254816999905282",
"title": "Research & Insights",
"url": "https://www.bridgewater.com/research-and-insights.rss",
"language": "English"
},
{
"feedId": "41147805276726402",
"title": "RSSHub has new routes",
"url": "rsshub://rsshub/routes",
"language": "English"
},
{
"feedId": "66860831563739166",
"title": "Sabine Hossenfelder",
"url": "https://www.youtube.com/feeds/videos.xml?channel_id=UC1yNl2E66ZzKApQdRuTQ4tw",
"language": "English",
"view": 3
},
{
"feedId": "49470377330653207",
"title": "Seth's Blog",
"url": "https://seths.blog/feed/",
"language": "English"
},
{
"feedId": "41795937311945747",
"title": "Stratechery by Ben Thompson",
"url": "https://stratechery.com/feed/",
"language": "English"
},
{
"feedId": "59299066394742784",
"title": "TechCrunch",
"url": "https://techcrunch.com/feed/",
"language": "English"
},
{
"feedId": "49470377330653193",
"title": "The Blog of Author Tim Ferriss",
"url": "https://tim.blog/feed/",
"language": "English"
},
{
"feedId": "57993144602143744",
"title": "The Memo by Howard Marks",
"url": "https://rss.art19.com/the-memo-by-howard-marks",
"language": "English"
},
{
"feedId": "100080366298125312",
"title": "The Verge",
"url": "https://www.theverge.com/rss/index.xml",
"language": "English"
},
{
"feedId": "41572238278099968",
"title": "The world in brief | The Economist",
"url": "rsshub://economist/espresso",
"language": "English"
},
{
"feedId": "41368476124603392",
"title": "Trending repositories on GitHub this week · GitHub",
"url": "rsshub://github/trending/weekly/any",
"language": "English"
},
{
"feedId": "41461870197170196",
"title": "Trending repositories on GitHub today · GitHub",
"url": "rsshub://github/trending/daily/any",
"language": "English"
},
{
"feedId": "87076619508029440",
"title": "Twitter @Balaji",
"url": "rsshub://twitter/user/balajis",
"language": "English",
"view": 1
},
{
"feedId": "59795737540541440",
"title": "Twitter @David Sinclair",
"url": "rsshub://twitter/user/davidasinclair",
"language": "English",
"view": 1
},
{
"feedId": "100411504863520768",
"title": "Twitter @Elon Musk",
"url": "rsshub://twitter/user/elonmusk",
"language": "English",
"view": 1
},
{
"feedId": "41215396143077382",
"title": "Twitter @Follow",
"url": "rsshub://twitter/user/follow_app_",
"language": "English",
"view": 1
},
{
"feedId": "55866936513101824",
"title": "Twitter @James Clear",
"url": "rsshub://twitter/user/JamesClear",
"language": "English",
"view": 1
},
{
"feedId": "41342818712721408",
"title": "Wait But Why",
"url": "https://waitbutwhy.com/feed?code=c6f56c4214621ab98b86acbcae6b4405",
"language": "English"
},
{
"feedId": "41965184796582014",
"title": "WIRED",
"url": "https://www.wired.com/feed/rss",
"language": "English"
}
]

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,8 @@
import { FeedViewType } from "@follow/constants"
import { getLocales } from "expo-localization"
import feeds from "./feeds.json"
import englishFeeds from "./feeds-english.json"
export type PresetFeedConfig = {
title: string
@ -7,116 +11,21 @@ export type PresetFeedConfig = {
view: FeedViewType
}
export const presetFeeds: PresetFeedConfig[] = [
{
feedId: "41358761177015296",
title: "知乎热榜 - 全站",
url: "rsshub://zhihu/hot/total",
view: FeedViewType.Articles,
},
{
feedId: "100020530265058357",
title: "阮一峰的网络日志",
url: "https://feeds.feedburner.com/ruanyifeng",
view: FeedViewType.Articles,
},
export const englishPresetFeeds: PresetFeedConfig[] = englishFeeds.map((feed) => ({
view: FeedViewType.Articles,
...feed,
}))
{
feedId: "41358830592746496",
title: "微博热搜榜",
url: "rsshub://weibo/search/hot",
view: FeedViewType.SocialMedia,
},
{
feedId: "100411504863520768",
title: "Twitter @Elon Musk",
url: "rsshub://twitter/user/elonmusk",
view: FeedViewType.SocialMedia,
},
{
feedId: "41324816676184077",
title: "Twitter @DIŸgöd ☀️",
url: "rsshub://twitter/user/DIYgod",
view: FeedViewType.SocialMedia,
},
export const otherPresetFeeds: PresetFeedConfig[] = feeds
// .filter((feed) => feed.language === "Chinese")
.map((feed) => ({
view: FeedViewType.Articles,
...feed,
}))
{
feedId: "78806242632741888",
title: "bilibili 排行榜-全站",
url: "rsshub://bilibili/ranking/0",
view: FeedViewType.Videos,
},
const locales = getLocales()
// `getLocales` guaranteed to contain at least 1 element.
const languageTag = locales[0]?.languageTag || "en-US"
const isEnglishUser = languageTag.startsWith("en")
{
feedId: "60338304723722240",
title: "实时财经快讯 - FastBull",
url: "rsshub://fastbull/express-news",
view: FeedViewType.Articles,
},
{
feedId: "55611390687386624",
title: "格隆汇快讯-7x24小时市场快讯-财经市场热点",
url: "rsshub://gelonghui/live",
view: FeedViewType.Articles,
},
{
feedId: "49375919416104960",
title: "深潮TechFlow - 快讯",
url: "rsshub://techflowpost/express",
view: FeedViewType.Articles,
},
{
feedId: "55982073122828305",
title: "TED Talks Daily",
url: "https://feeds.acast.com/public/shows/67587e77c705e441797aff96",
view: FeedViewType.Articles,
},
{
feedId: "72541715399995392",
title: "TheBlockBeats - 快讯",
url: "rsshub://theblockbeats/newsflash/0",
view: FeedViewType.Articles,
},
{
feedId: "100184911354754055",
title: "小Lin说",
url: "https://www.youtube.com/feeds/videos.xml?channel_id=UCilwQlk62k1z7aUEZPOB6yw",
view: FeedViewType.Videos,
},
{
feedId: "100185810923910148",
title: "张小珺Jùn商业访谈录",
url: "https://feed.xyzfm.space/dk4yh3pkpjp3",
view: FeedViewType.Articles,
},
{
feedId: "56584656988676096",
title: "迷因电波",
url: "rsshub://xiaoyuzhou/podcast/61d52b3bee197a3aac3dac44",
view: FeedViewType.Articles,
},
{
feedId: "76051724651752448",
title: "极致音乐汇 的 bilibili 空间",
url: "rsshub://bilibili/user/video/1691501735",
view: FeedViewType.Videos,
},
{
feedId: "66701376672681984",
title: "AP Top News - AP News",
url: "rsshub://apnews/api/apf-topnews",
view: FeedViewType.Articles,
},
{
feedId: "44366244616936448",
title: "金十数据",
url: "rsshub://jin10",
view: FeedViewType.Articles,
},
{
feedId: "52325519371718656",
title: "Hacker News",
url: "rsshub://hackernews",
view: FeedViewType.Articles,
},
]
export const presetFeeds = isEnglishUser ? englishPresetFeeds : otherPresetFeeds

View File

@ -5,7 +5,6 @@ import Animated, { FadeIn, FadeOut } from "react-native-reanimated"
import { Search3CuteReIcon } from "@/src/icons/search_3_cute_re"
import { Shuffle2CuteReIcon } from "@/src/icons/shuffle_2_cute_re"
import { toast } from "@/src/lib/toast"
import { useSubscription } from "@/src/store/subscription/hooks"
import { subscriptionSyncService } from "@/src/store/subscription/store"
import { accentColor } from "@/src/theme/colors"
@ -13,28 +12,6 @@ import { accentColor } from "@/src/theme/colors"
import type { PresetFeedConfig } from "./preset"
import { presetFeeds } from "./preset"
const subscribeFeed = async (config: PresetFeedConfig) => {
await subscriptionSyncService.subscribe({
feedId: config.feedId,
title: config.title,
url: config.url,
view: config.view,
category: "",
isPrivate: false,
})
toast.success(`Subscribed to ${config.title}`, {
position: "bottom",
})
}
const unsubscribeFeed = async (feedId: string) => {
await subscriptionSyncService.unsubscribe(feedId)
toast.success(`Unsubscribed from feed`, {
position: "bottom",
})
}
export const StepInterests = () => {
const [displayFeeds, setDisplayFeeds] = useState<PresetFeedConfig[]>(presetFeeds.slice(0, 7))
@ -66,7 +43,7 @@ export const StepInterests = () => {
<View className="flex-row flex-wrap justify-center gap-2 px-4">
{displayFeeds.map((feed) => (
<FeedChip key={feed.feedId} {...feed} />
<FeedChip key={feed.feedId} feed={feed} />
))}
</View>
</View>
@ -74,18 +51,43 @@ export const StepInterests = () => {
)
}
const FeedChip = (feed: PresetFeedConfig) => {
const isSubscribed = useSubscription(feed.feedId)
const FeedChip = ({ feed }: { feed: PresetFeedConfig }) => {
const subscription = useSubscription(feed.feedId)
const [loading, setLoading] = useState(false)
const [isSubscribed, setIsSubscribed] = useState(!!subscription)
const handleSubscribe = useCallback(
async (feed: PresetFeedConfig) => {
if (loading) return
setLoading(true)
if (isSubscribed) {
await unsubscribeFeed(feed.feedId)
try {
setIsSubscribed(false)
await subscriptionSyncService.unsubscribe(feed.feedId)
} catch {
setIsSubscribed(true)
} finally {
setLoading(false)
}
return
}
await subscribeFeed(feed)
try {
setIsSubscribed(true)
await subscriptionSyncService.subscribe({
feedId: feed.feedId,
title: feed.title,
url: feed.url,
view: feed.view,
category: "",
isPrivate: false,
})
} catch {
setIsSubscribed(false)
} finally {
setLoading(false)
}
},
[isSubscribed],
[isSubscribed, loading],
)
return (

View File

@ -25,8 +25,9 @@ export const useUser = (userId?: string) => {
return useUserStore((state) => (userId ? state.users[userId] : undefined))
}
export function useIsNewUser() {
export function useIsNewUser(enabled = true) {
const { data } = useQuery({
enabled,
queryKey: isNewUserQueryKey,
queryFn: async () => {
const isOnboardingFinished = await kv.get(isOnboardingFinishedStorageKey)
@ -42,7 +43,8 @@ export function useIsNewUser() {
}
export function useOnboarding() {
const isNewUser = useIsNewUser()
const whoami = useWhoami()
const isNewUser = useIsNewUser(!!whoami)
useEffect(() => {
if (isNewUser) {
router.push("/onboarding")