Files
RN_Template/RN_TEMPLATE/app/context/EpisodeContext.tsx

135 lines
3.8 KiB
TypeScript
Raw Normal View History

2026-02-05 13:16:05 +08:00
import {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
useMemo,
useState,
} from "react"
import { translate } from "@/i18n/translate"
import { api } from "@/services/api"
import type { EpisodeItem } from "@/services/api/types"
import { formatDate } from "@/utils/formatDate"
export type EpisodeContextType = {
totalEpisodes: number
totalFavorites: number
episodesForList: EpisodeItem[]
fetchEpisodes: () => Promise<void>
favoritesOnly: boolean
toggleFavoritesOnly: () => void
hasFavorite: (episode: EpisodeItem) => boolean
toggleFavorite: (episode: EpisodeItem) => void
}
export const EpisodeContext = createContext<EpisodeContextType | null>(null)
export interface EpisodeProviderProps {}
export const EpisodeProvider: FC<PropsWithChildren<EpisodeProviderProps>> = ({ children }) => {
const [episodes, setEpisodes] = useState<EpisodeItem[]>([])
const [favorites, setFavorites] = useState<string[]>([])
const [favoritesOnly, setFavoritesOnly] = useState<boolean>(false)
const fetchEpisodes = useCallback(async () => {
const response = await api.getEpisodes()
if (response.kind === "ok") {
setEpisodes(response.episodes)
} else {
console.error(`Error fetching episodes: ${JSON.stringify(response)}`)
}
}, [])
const toggleFavoritesOnly = useCallback(() => {
setFavoritesOnly((prev) => !prev)
}, [])
const toggleFavorite = useCallback(
(episode: EpisodeItem) => {
if (favorites.some((fav) => fav === episode.guid)) {
setFavorites((prev) => prev.filter((fav) => fav !== episode.guid))
} else {
setFavorites((prev) => [...prev, episode.guid])
}
},
[favorites],
)
const hasFavorite = useCallback(
(episode: EpisodeItem) => favorites.some((fav) => fav === episode.guid),
[favorites],
)
const episodesForList = useMemo(() => {
return favoritesOnly ? episodes.filter((episode) => favorites.includes(episode.guid)) : episodes
}, [episodes, favorites, favoritesOnly])
const value = {
totalEpisodes: episodes.length,
totalFavorites: favorites.length,
episodesForList,
fetchEpisodes,
favoritesOnly,
toggleFavoritesOnly,
hasFavorite,
toggleFavorite,
}
return <EpisodeContext.Provider value={value}>{children}</EpisodeContext.Provider>
}
export const useEpisodes = () => {
const context = useContext(EpisodeContext)
if (!context) throw new Error("useEpisodes must be used within an EpisodeProvider")
return context
}
// A helper hook to extract and format episode details
export const useEpisode = (episode: EpisodeItem) => {
const { hasFavorite } = useEpisodes()
const isFavorite = hasFavorite(episode)
let datePublished
try {
const formatted = formatDate(episode.pubDate)
datePublished = {
textLabel: formatted,
accessibilityLabel: translate("demoPodcastListScreen:accessibility.publishLabel", {
date: formatted,
}),
}
} catch {
datePublished = { textLabel: "", accessibilityLabel: "" }
}
const seconds = Number(episode.enclosure?.duration ?? 0)
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = Math.floor((seconds % 3600) % 60)
const duration = {
textLabel: `${h > 0 ? `${h}:` : ""}${m > 0 ? `${m}:` : ""}${s}`,
accessibilityLabel: translate("demoPodcastListScreen:accessibility.durationLabel", {
hours: h,
minutes: m,
seconds: s,
}),
}
const trimmedTitle = episode.title?.trim()
const titleMatches = trimmedTitle?.match(/^(RNR.*\d)(?: - )(.*$)/)
const parsedTitleAndSubtitle =
titleMatches && titleMatches.length === 3
? { title: titleMatches[1], subtitle: titleMatches[2] }
: { title: trimmedTitle, subtitle: "" }
return {
isFavorite,
datePublished,
duration,
parsedTitleAndSubtitle,
}
}