jellyfin-expo/screens/HomeScreen.js
2022-05-04 12:40:11 -04:00

231 lines
7.3 KiB
JavaScript

/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { useFocusEffect, useNavigation } from '@react-navigation/native';
import { observer } from 'mobx-react-lite';
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { BackHandler, Platform, StyleSheet, View } from 'react-native';
import { ThemeContext } from 'react-native-elements';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import AudioPlayer from '../components/AudioPlayer';
import ErrorView from '../components/ErrorView';
import NativeShellWebView from '../components/NativeShellWebView';
import VideoPlayer from '../components/VideoPlayer';
import Colors from '../constants/Colors';
import MediaTypes from '../constants/MediaTypes';
import Screens from '../constants/Screens';
import { useStores } from '../hooks/useStores';
import { getIconName } from '../utils/Icons';
const HomeScreen = observer(() => {
const { rootStore } = useStores();
const navigation = useNavigation();
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const { theme } = useContext(ThemeContext);
const [ isLoading, setIsLoading ] = useState(true);
const [ httpErrorStatus, setHttpErrorStatus ] = useState(null);
const webview = useRef(null);
useEffect(() => {
// Pressing the Home tab when it is already active navigates to home screen in webview
navigation.getParent()?.addListener('tabPress', e => {
if (navigation.isFocused()) {
// Prevent default behavior
e.preventDefault();
// Call the web router to navigate home
webview.current?.injectJavaScript('window.ExpoRouterShim && window.ExpoRouterShim.home();');
}
});
}, []);
useFocusEffect(
useCallback(() => {
const onBackPress = () => {
webview.current?.injectJavaScript('window.ExpoRouterShim && window.ExpoRouterShim.back();');
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, [ webview ])
);
// Report media updates to the audio/video plugin
useEffect(() => {
if (!rootStore.mediaStore.isLocalFile) {
const status = {
didPlayerCloseManually: rootStore.didPlayerCloseManually,
uri: rootStore.mediaStore.uri,
isFinished: rootStore.mediaStore.isFinished,
isPlaying: rootStore.mediaStore.isPlaying,
positionTicks: rootStore.mediaStore.positionTicks,
positionMillis: rootStore.mediaStore.positionMillis
};
if (rootStore.mediaStore.type === MediaTypes.Audio) {
webview.current?.injectJavaScript(`window.ExpoAudioPlayer && window.ExpoAudioPlayer._reportStatus(${JSON.stringify(status)});`);
} else if (rootStore.mediaStore.type === MediaTypes.Video) {
webview.current?.injectJavaScript(`window.ExpoVideoPlayer && window.ExpoVideoPlayer._reportStatus(${JSON.stringify(status)});`);
}
}
}, [
rootStore.mediaStore.type,
rootStore.mediaStore.uri,
rootStore.mediaStore.isFinished,
rootStore.mediaStore.isLocalFile,
rootStore.mediaStore.isPlaying,
rootStore.mediaStore.positionTicks
]);
// Clear the error state when the active server changes
useEffect(() => {
setIsLoading(true);
}, [ rootStore.settingStore.activeServer ]);
useEffect(() => {
if (rootStore.isReloadRequired) {
webview.current?.reload();
rootStore.isReloadRequired = false;
}
}, [ rootStore.isReloadRequired ]);
useEffect(() => {
if (httpErrorStatus) {
const errorCode = httpErrorStatus.description || httpErrorStatus.statusCode;
navigation.replace(Screens.ErrorScreen, {
icon: {
name: 'cloud-off',
type: 'material'
},
heading: t([ `home.errors.${errorCode}.heading`, 'home.errors.http.heading' ]),
message: t([ `home.errors.${errorCode}.description`, 'home.errors.http.description' ]),
details: [
t('home.errorCode', { errorCode }),
t('home.errorUrl', { url: httpErrorStatus.url })
],
buttonIcon: {
name: getIconName('refresh'),
type: 'ionicon'
},
buttonTitle: t('home.retry')
});
}
}, [ httpErrorStatus ]);
// When not in fullscreen, the top adjustment is handled by the spacer View for iOS
const safeAreaEdges = [ 'right', 'left' ];
if (Platform.OS !== 'ios' || rootStore.isFullscreen) {
safeAreaEdges.push('top');
}
// Bottom spacer is handled by tab bar except in fullscreen
if (rootStore.isFullscreen) {
safeAreaEdges.push('bottom');
}
// Hide webview until loaded
const webviewStyle = (isLoading || httpErrorStatus) ? StyleSheet.compose(styles.container, styles.loading) : styles.container;
if (!rootStore.serverStore.servers || rootStore.serverStore.servers.length === 0) {
return null;
}
const server = rootStore.serverStore.servers[rootStore.settingStore.activeServer];
return (
<SafeAreaView
style={{
...styles.container,
backgroundColor: rootStore.isFullscreen ? Colors.black : theme.colors.background
}}
edges={safeAreaEdges}
>
{Platform.OS === 'ios' && !rootStore.isFullscreen && (
<View style={{
backgroundColor: theme.colors.grey0,
height: insets.top
}} />
)}
{server && server.urlString ? (
<>
<NativeShellWebView
ref={webview}
style={webviewStyle}
containerStyle={webviewStyle}
refreshControlProps={{
// iOS colors
tintColor: theme.colors.grey1,
backgroundColor: theme.colors.grey0,
// Android colors
colors: [ theme.colors.primary, theme.colors.secondary ],
progressBackgroundColor: theme.colors.background
}}
// Error screen is displayed if loading fails
renderError={errorCode => (
<ErrorView
icon={{
name: 'cloud-off',
type: 'material'
}}
heading={t([ `home.errors.${errorCode}.heading`, 'home.errors.offline.heading' ])}
message={t([ `home.errors.${errorCode}.description`, 'home.errors.offline.description' ])}
details={[
t('home.errorCode', { errorCode }),
t('home.errorUrl', { url: server.urlString })
]}
buttonIcon={{
name: getIconName('refresh'),
type: 'ionicon'
}}
buttonTitle={t('home.retry')}
onPress={() => webview.current?.reload()}
/>
)}
// Loading screen is displayed when refreshing
renderLoading={() => <View style={styles.container} />}
// Update state on loading error
onError={({ nativeEvent: state }) => {
console.warn('Error', state);
}}
onHttpError={({ nativeEvent: state }) => {
console.warn('HTTP Error', state);
setHttpErrorStatus(state);
}}
onLoadStart={() => {
setIsLoading(true);
setHttpErrorStatus(null);
}}
// Update state when loading is complete
onLoadEnd={() => {
setIsLoading(false);
}}
/>
<AudioPlayer/>
<VideoPlayer/>
</>
) : (
<ErrorView
heading={t('home.errors.invalidServer.heading')}
message={t('home.errors.invalidServer.description')}
/>
)}
</SafeAreaView>
);
});
const styles = StyleSheet.create({
container: {
flex: 1
},
loading: {
opacity: 0
}
});
export default HomeScreen;