diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx index d828d2fa..4c57a709 100644 --- a/src/config/lexics/indexLexics.tsx +++ b/src/config/lexics/indexLexics.tsx @@ -75,6 +75,7 @@ export const indexLexics = { available_matches_shown: 13385, basketball: 6960, broadcast: 13049, + check_connection: 15700, cm: 817, features: 13051, football: 6958, @@ -96,6 +97,7 @@ export const indexLexics = { live: 13024, loading: 3527, logout: 4306, + lost_connection: 15699, match_status_finished: 12985, match_status_live: 12984, match_status_soon: 12986, diff --git a/src/features/App/AuthenticatedApp.tsx b/src/features/App/AuthenticatedApp.tsx index e861c774..a25d174b 100644 --- a/src/features/App/AuthenticatedApp.tsx +++ b/src/features/App/AuthenticatedApp.tsx @@ -20,6 +20,7 @@ import { MatchPopup, MatchPopupStore } from 'features/MatchPopup' import { BuyMatchPopup, BuyMatchPopupStore } from 'features/BuyMatchPopup' import { PreferencesPopup, PreferencesPopupStore } from 'features/PreferencesPopup' import { CardsStore } from 'features/CardsStore' +import { NoNetworkPopup, NoNetworkPopupStore } from 'features/NoNetworkPopup' const HomePage = lazy(() => import('features/HomePage')) const UserAccount = lazy(() => import('features/UserAccount')) @@ -40,32 +41,35 @@ export const AuthenticatedApp = () => { - - - + + + + + - {/* в Switch как прямой children можно рендерить только Route или Redirect */} - - - - - - - - - - - - - - - - - - - - - + {/* в Switch как прямой children можно рендерить только Route или Redirect */} + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/features/Background/index.tsx b/src/features/Background/index.tsx index 4c6aabfe..5381a717 100644 --- a/src/features/Background/index.tsx +++ b/src/features/Background/index.tsx @@ -1,5 +1,7 @@ import { ReactNode } from 'react' +import { FakeArrowLoader } from 'features/NoNetworkPopup/styled' + import { GradientBackground, ImageBackground } from './styled' type Props = { @@ -9,6 +11,9 @@ type Props = { export const Background = ({ children }: Props) => ( + {/* FakeArrowLoader пришлось добавить, чтобы при отсутствии интернета + всегда была доступена svg arrowGroup для отображения лоадера в NoNetwork */} + {children} diff --git a/src/features/MultiSourcePlayer/hooks/index.tsx b/src/features/MultiSourcePlayer/hooks/index.tsx index e7ad840f..afe939f5 100644 --- a/src/features/MultiSourcePlayer/hooks/index.tsx +++ b/src/features/MultiSourcePlayer/hooks/index.tsx @@ -10,6 +10,7 @@ import size from 'lodash/size' import { useControlsVisibility } from 'features/StreamPlayer/hooks/useControlsVisibility' import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen' import { useVolume } from 'features/VideoPlayer/hooks/useVolume' +import { useNoNetworkPopupStore } from 'features/NoNetworkPopup' import { useObjectState } from 'hooks' @@ -70,6 +71,7 @@ export const useMultiSourcePlayer = ({ onReady, playNextChapter, playPrevChapter, + stopPlaying, togglePlaying, } = usePlayingHandlers(setPlayerState, numberOfChapters) @@ -203,6 +205,17 @@ export const useMultiSourcePlayer = ({ playNextChapter, ]) + const { isOnline } = useNoNetworkPopupStore() + + useEffect(() => { + if (!isOnline) { + stopPlaying() + } + }, [ + isOnline, + stopPlaying, + ]) + return { activeChapterIndex, activePlayer, diff --git a/src/features/NoNetworkPopup/index.tsx b/src/features/NoNetworkPopup/index.tsx new file mode 100644 index 00000000..3ad1b6c6 --- /dev/null +++ b/src/features/NoNetworkPopup/index.tsx @@ -0,0 +1,32 @@ +import { useNoNetworkPopupStore } from 'features/NoNetworkPopup' +import { Background } from 'features/Background' + +import { + ArrowLoader, + Container, + Logo, + SubTitle, + Title, + Wrapper, +} from './styled' + +export * from './store' + +export const NoNetworkPopup = () => { + const { isOnline } = useNoNetworkPopupStore() + + if (isOnline) return null + + return ( + + + + + + + <SubTitle t='check_connection' /> + </Container> + </Background> + </Wrapper> + ) +} diff --git a/src/features/NoNetworkPopup/store/hooks/index.tsx b/src/features/NoNetworkPopup/store/hooks/index.tsx new file mode 100644 index 00000000..4d2b098d --- /dev/null +++ b/src/features/NoNetworkPopup/store/hooks/index.tsx @@ -0,0 +1,23 @@ +import { useState } from 'react' + +import { useEventListener } from 'hooks' + +export const useNetwork = () => { + const [isOnline, setNetwork] = useState(window.navigator.onLine) + + const updateNetwork = () => { + setNetwork(window.navigator.onLine) + } + + useEventListener({ + callback: updateNetwork, + event: 'offline', + }) + + useEventListener({ + callback: updateNetwork, + event: 'online', + }) + + return { isOnline } +} diff --git a/src/features/NoNetworkPopup/store/index.tsx b/src/features/NoNetworkPopup/store/index.tsx new file mode 100644 index 00000000..75852472 --- /dev/null +++ b/src/features/NoNetworkPopup/store/index.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' +import { createContext, useContext } from 'react' + +import { useNetwork } from './hooks' + +type Context = ReturnType<typeof useNetwork> +type Props = { children: ReactNode } + +const NoNetworkPopupContext = createContext({} as Context) + +export const NoNetworkPopupStore = ({ children }: Props) => { + const value = useNetwork() + return ( + <NoNetworkPopupContext.Provider value={value}> + {children} + </NoNetworkPopupContext.Provider> + ) +} + +export const useNoNetworkPopupStore = () => useContext(NoNetworkPopupContext) diff --git a/src/features/NoNetworkPopup/styled.tsx b/src/features/NoNetworkPopup/styled.tsx new file mode 100644 index 00000000..50163c6b --- /dev/null +++ b/src/features/NoNetworkPopup/styled.tsx @@ -0,0 +1,81 @@ +import styled from 'styled-components/macro' + +import { Arrows } from 'features/ArrowLoader/styled' +import { T9n } from 'features/T9n' + +export const Wrapper = styled.div` + position: absolute; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + z-index: 1000; +` + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #FFFFFF; +` + +export const Logo = styled.div` + width: 234px; + height: 54px; + background-size: contain; + background-repeat: no-repeat; + background-image: url(/images/logo.svg); + margin-bottom: 87px; + + @media (max-width: 850px) { + width: 144px; + height: 33px; + margin-bottom: 54px; + } +` + +export const FakeArrowLoader = styled.div` + width: 0; + height: 0; + visibility: hidden; + background-image: url(/images/arrowGroup.svg); +` + +export const ArrowLoader = styled(Arrows)` + width: 94px; + height: 87px; + + @media (max-width: 850px) { + width: 58px; + height: 54px; + } +` + +export const Title = styled(T9n)` + font-weight: bold; + font-size: 24px; + line-height: 24px; + margin-top: 87px; + + @media (max-width: 850px) { + font-size: 14px; + line-height: 15px; + margin-top: 54px; + } +` + +export const SubTitle = styled(T9n)` + font-weight: 500; + font-size: 16px; + line-height: 20px; + letter-spacing: 0.03em; + opacity: 0.8; + margin-top: 12px; + + @media (max-width: 850px) { + font-size: 10px; + line-height: 12px; + margin-top: 7px; + } +` diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index 88ee0e3f..86c5afbc 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -1,10 +1,15 @@ import type { MouseEvent } from 'react' -import { useCallback, useMemo } from 'react' +import { + useCallback, + useEffect, + useMemo, +} from 'react' import once from 'lodash/once' import { useVolume } from 'features/VideoPlayer/hooks/useVolume' import { REWIND_SECONDS } from 'features/MultiSourcePlayer/config' +import { useNoNetworkPopupStore } from 'features/NoNetworkPopup' import { useObjectState } from 'hooks' @@ -112,6 +117,19 @@ export const useVideoPlayer = ({ setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 }) }, [duration, setPlayerState]) + const { isOnline } = useNoNetworkPopupStore() + + useEffect(() => { + if (!isOnline) { + setPlayerState({ playing: false }) + onPlayingChange(false) + } + }, [ + isOnline, + onPlayingChange, + setPlayerState, + ]) + return { backToLive, duration,