135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
|
|
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,
|
||
|
|
}
|
||
|
|
}
|