Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 34x 34x 34x 1x 40x 40x 6x 1x 1x 40x 40x 40x | // SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 Max Boksem. See NOTICE for additional terms under GPLv3 section 7.
import { Routes, Route } from "react-router";
import { AppLayout } from "./components/AppLayout";
import { PlayerView } from "./components/player/VideoPlayer";
import { ChannelList } from "./components/channels/ChannelList";
import { PlaylistManager } from "./components/playlist/PlaylistManager";
import { Settings } from "./components/settings/Settings";
import { UpdateBanner } from "./components/UpdateBanner";
import { SplashScreen } from "./components/SplashScreen";
import { DonationPopup } from "./components/DonationPopup";
import { ChannelsContext, useChannels, useChannelsProvider } from "./hooks/useChannels";
import { useUpdateChecker } from "./hooks/useUpdateChecker";
import { useSplashScreen } from "./hooks/useSplashScreen";
import { useDonationPrompt } from "./hooks/useDonationPrompt";
import { useLockScreenSkip } from "./hooks/useLockScreenSkip";
import { FullscreenProvider } from "./lib/fullscreen-context";
import { DownloadsProvider } from "@/hooks/useDownloads";
import { isMobilePlatform } from "@/lib/platform";
export default function App() {
const channelsValue = useChannelsProvider();
const updateState = useUpdateChecker();
return (
<ChannelsContext.Provider value={channelsValue}>
<FullscreenProvider>
<DownloadsProvider>
<AppRoutes updateState={updateState} />
</DownloadsProvider>
</FullscreenProvider>
</ChannelsContext.Provider>
);
}
// Inner component so useSplashScreen can access ChannelsContext via useChannels.
interface AppRoutesProps {
updateState: ReturnType<typeof useUpdateChecker>;
}
const AppRoutes = ({ updateState }: AppRoutesProps) => {
const { refreshProviders, refreshChannels } = useChannels();
const splash = useSplashScreen({
updateState,
// After splash finishes any playlist/EPG refreshes, sync ChannelsContext so
// polling in useChannels reads fresh lastUpdated timestamps (avoids re-triggering
// the same refresh 60s later due to stale state).
onComplete: (didRefreshProviders) => {
if (didRefreshProviders) {
refreshProviders().catch(() => {});
refreshChannels().catch(() => {});
}
},
});
useLockScreenSkip();
// No donation prompt on mobile, where App Store rules don't allow one.
const donation = useDonationPrompt({ enabled: splash.dismissed && !isMobilePlatform() });
return (
<>
{!splash.dismissed && <SplashScreen splash={splash} updateState={updateState} />}
<Routes>
<Route element={<AppLayout />}>
<Route path="/" element={<ChannelList />} />
<Route path="/player" element={<PlayerView />} />
<Route path="/playlists" element={<PlaylistManager />} />
<Route path="/settings" element={<Settings updateState={updateState} />} />
</Route>
</Routes>
<UpdateBanner state={updateState} hidden={!splash.dismissed} />
{donation.shouldShow && !updateState.update && (
<DonationPopup onDismiss={donation.dismiss} />
)}
</>
);
};
|