Merge pull request #66 from thornbill/updates

Update dependencies
This commit is contained in:
Bill Thornton 2020-04-22 10:16:56 -04:00 committed by GitHub
commit 5a584b4c82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1083 additions and 1628 deletions

View File

@ -35,7 +35,7 @@
}
},
"rules": {
"react/prop-types": ["error", { "ignore": ["navigation"] }],
"react/prop-types": ["error"],
"block-spacing": ["error"],
"brace-style": ["error"],

36
App.js
View File

@ -23,7 +23,7 @@ export default class App extends React.Component {
};
state = {
isLoadingComplete: false
isSplashReady: false
};
componentDidMount() {
@ -36,25 +36,25 @@ export default class App extends React.Component {
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
if (!this.state.isSplashReady && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
onError={console.warn}
onFinish={() => this.setState({ isSplashReady: true })}
autoHideSplash={false}
/>
);
} else {
return (
<ThemeProvider theme={Theme}>
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="light-content" />}
<AppNavigator />
</View>
</ThemeProvider>
);
}
return (
<ThemeProvider theme={Theme}>
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="light-content" />}
<AppNavigator />
</View>
</ThemeProvider>
);
}
_loadImagesAsync = () => {
@ -74,16 +74,6 @@ export default class App extends React.Component {
...this._loadImagesAsync()
]);
};
_handleLoadingError = error => {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
console.warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
const styles = StyleSheet.create({

View File

@ -1,27 +0,0 @@
/**
* 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 'react-native';
import React from 'react';
import App from '../App';
import renderer from 'react-test-renderer';
import NavigationTestUtils from 'react-navigation/NavigationTestUtils';
describe('App snapshot', () => {
jest.useFakeTimers();
beforeEach(() => {
NavigationTestUtils.resetInternalState();
});
it('renders the loading screen', async () => {
const tree = renderer.create(<App />).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders the root without loading screen', async () => {
const tree = renderer.create(<App skipLoadingScreen />).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@ -6,6 +6,7 @@
import React from 'react';
import { ActivityIndicator, Platform, StyleSheet } from 'react-native';
import { Input, colors } from 'react-native-elements';
import { useNavigation } from '@react-navigation/native';
import PropTypes from 'prop-types';
import Colors from '../constants/Colors';
@ -15,7 +16,7 @@ import JellyfinValidator from '../utils/JellyfinValidator';
const sanitizeHost = (url = '') => url.trim();
export default class ServerInput extends React.Component {
class ServerInput extends React.Component {
static propTypes = {
navigation: PropTypes.object.isRequired,
onSuccess: PropTypes.func,
@ -126,3 +127,11 @@ const styles = StyleSheet.create({
borderBottomWidth: 0
}
});
// Inject the Navigation Hook as a prop to mimic the legacy behavior
const ServerInputWithNavigation = function(props) {
const navigation = useNavigation();
return <ServerInput {...props} navigation={navigation} />;
};
export default ServerInputWithNavigation;

View File

@ -1,28 +0,0 @@
/**
* 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 React from 'react';
import { Ionicons } from '@expo/vector-icons';
import PropTypes from 'prop-types';
import Colors from '../constants/Colors';
export default class TabBarIcon extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
focused: PropTypes.bool.isRequired
};
render() {
return (
<Ionicons
name={this.props.name}
size={26}
style={{ marginBottom: -3 }}
color={this.props.focused ? Colors.tabIconSelected : Colors.tabIconDefault}
/>
);
}
}

View File

@ -12,9 +12,7 @@ export default {
backgroundColor,
tintColor,
headerTintColor: textColor,
tabIconDefault: '#ccc',
tabIconSelected: tintColor,
tabBar: '#fefefe',
tabText: '#ccc',
errorBackground: 'red',
errorText: '#fff',
warningBackground: '#EAEB5E',

View File

@ -3,16 +3,108 @@
* 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 { createAppContainer, createSwitchNavigator } from 'react-navigation';
import React, { useState } from 'react';
import { NavigationContainer, DarkTheme } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { SplashScreen } from 'expo';
import { Ionicons } from '@expo/vector-icons';
import MainTabNavigator from './MainTabNavigator';
import Colors from '../constants/Colors';
import StorageKeys from '../constants/Storage';
import CachingStorage from '../utils/CachingStorage';
import AddServerScreen from '../screens/AddServerScreen';
import ServerLoadingScreen from '../screens/ServerLoadingScreen';
import HomeScreen from '../screens/HomeScreen';
import LoadingScreen from '../screens/LoadingScreen';
import SettingsScreen from '../screens/SettingsScreen';
export default createAppContainer(createSwitchNavigator({
ServerLoading: ServerLoadingScreen,
AddServer: AddServerScreen,
Main: MainTabNavigator
}, {
initialRouteName: 'ServerLoading'
}));
// Customize theme for navigator
const theme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: Colors.tintColor,
background: Colors.backgroundColor,
text: Colors.tabText,
border: 'transparent'
}
};
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
function TabIcon(routeName, color, size) {
let iconName = null;
if (routeName === 'Home') {
iconName = 'ios-tv';
} else if (routeName === 'Settings') {
iconName = 'ios-cog';
}
return (
iconName ? <Ionicons name={iconName} color={color} size={size} /> : null
);
}
function Main() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ color, size }) => TabIcon(route.name, color, size)
})}
>
<Tab.Screen name='Home' component={HomeScreen} />
<Tab.Screen name='Settings' component={SettingsScreen} />
</Tab.Navigator>
);
}
function AppNavigator() {
const [isLoading, setIsLoading] = useState(true);
const [servers, setServers] = useState(null);
async function bootstrap() {
// Fetch any saved servers
const savedServers = await CachingStorage.getInstance().getItem(StorageKeys.Servers);
setServers(savedServers);
setIsLoading(false);
}
bootstrap();
// Display the loading screen until bootstrapping is complete
if (isLoading) {
return <LoadingScreen />;
}
// Ensure the splash screen is hidden when loading is finished
SplashScreen.hide();
return (
<NavigationContainer theme={theme}>
<Stack.Navigator headerMode='screen' screenOptions={{ headerShown: false }}>
{(servers && servers.length > 0) ?
<Stack.Screen
name='Main'
component={Main}
options={({ route }) => {
const routeName = route.state ?
// Get the currently active route name in the tab navigator
route.state.routes[route.state.index].name :
// If state doesn't exist, we need to default to `screen` param if available, or the initial screen
// In our case, it's "Main" as that's the first screen inside the navigator
route.params?.screen || 'Main';
return ({
headerShown: routeName === 'Settings',
title: routeName
});
}}
/> :
<Stack.Screen name='AddServer' component={AddServerScreen} />
}
</Stack.Navigator>
</NavigationContainer>
);
}
export default AppNavigator;

View File

@ -1,91 +0,0 @@
/**
* 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 React from 'react';
import { Platform } from 'react-native';
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
import Colors from '../constants/Colors';
import TabBarIcon from '../components/TabBarIcon';
import HomeScreen from '../screens/HomeScreen';
import SettingsScreen from '../screens/SettingsScreen';
const defaultNavigationOptions = {
headerStyle: {
backgroundColor: Colors.backgroundColor
},
headerTintColor: Colors.headerTintColor
};
const HomeStack = createStackNavigator({
Home: HomeScreen
}, {
defaultNavigationOptions
});
HomeStack.navigationOptions = ({ navigation }) => {
const tabBarVisible = (navigation.state && navigation.state.routes[0].params) ?
navigation.state.routes[0].params.tabBarVisible : true;
return {
tabBarLabel: 'Home',
// eslint-disable-next-line react/display-name, react/prop-types
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? 'ios-tv'
: 'md-tv'
}
/>
),
tabBarVisible,
tabBarOnPress: ({ navigation, defaultHandler }) => {
const goHome = navigation.state && navigation.state.routes[0].params && navigation.state.routes[0].params.goHome;
if (navigation.isFocused() && typeof goHome === 'function') {
goHome();
} else {
defaultHandler();
}
}
};
};
const SettingsStack = createStackNavigator({
Settings: SettingsScreen
}, {
defaultNavigationOptions
});
SettingsStack.navigationOptions = {
tabBarLabel: 'Settings',
// eslint-disable-next-line react/display-name, react/prop-types
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? 'ios-cog'
: 'md-cog'
}
/>
)
};
export default createBottomTabNavigator({
HomeStack,
SettingsStack
}, {
tabBarOptions: {
activeTintColor: Colors.tabIconSelected,
inactiveTintColor: Colors.tabIconDefault,
style: {
backgroundColor: Colors.backgroundColor
},
// Force toolbar label to be under the icon
adaptive: false
}
});

View File

@ -12,6 +12,9 @@
},
"dependencies": {
"@react-native-community/masked-view": "0.1.6",
"@react-navigation/bottom-tabs": "^5.2.6",
"@react-navigation/native": "^5.1.5",
"@react-navigation/stack": "^5.2.10",
"expo": "^37.0.0",
"expo-asset": "~8.1.3",
"expo-constants": "~9.0.0",
@ -28,8 +31,7 @@
"react-native-reanimated": "~1.7.0",
"react-native-safe-area-context": "0.7.3",
"react-native-screens": "~2.2.0",
"react-native-webview": "8.1.1",
"react-navigation": "^3.0.9"
"react-native-webview": "8.1.1"
},
"devDependencies": {
"babel-eslint": "^10.0.2",
@ -39,7 +41,7 @@
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.14.3",
"expo-cli": "^3.17.17",
"expo-cli": "^3.18.2",
"jest-expo": "^37.0.0"
},
"private": true

View File

@ -21,7 +21,6 @@ export default class AddServerScreen extends React.Component {
/>
</View>
<ServerInput
navigation={this.props.navigation}
containerStyle={styles.serverTextContainer}
/>
</View>

View File

@ -7,8 +7,10 @@ import React from 'react';
import { Platform, RefreshControl, StatusBar, StyleSheet, ScrollView, View } from 'react-native';
import { Button, Text } from 'react-native-elements';
import { WebView } from 'react-native-webview';
import { useNavigation, useRoute } from '@react-navigation/native';
import Constants from 'expo-constants';
import * as ScreenOrientation from 'expo-screen-orientation';
import PropTypes from 'prop-types';
import Colors from '../constants/Colors';
import StorageKeys from '../constants/Storage';
@ -31,7 +33,7 @@ ${NativeShell}
true;
`;
export default class HomeScreen extends React.Component {
class HomeScreen extends React.Component {
state = {
server: null,
serverUrl: null,
@ -41,9 +43,10 @@ export default class HomeScreen extends React.Component {
isRefreshing: false
};
static navigationOptions = {
header: null
};
static propTypes = {
navigation: PropTypes.object.isRequired,
route: PropTypes.object.isRequired
}
async bootstrapAsync() {
let server = await CachingStorage.getInstance().getItem(StorageKeys.Servers);
@ -153,22 +156,29 @@ export default class HomeScreen extends React.Component {
}
componentDidMount() {
// Expose the goHome method to navigation props
this.props.navigation.setParams({ goHome: () => this.onGoHome() });
// Override the default tab press behavior so a second press sends the webview home
this.props.navigation.addListener('tabPress', e => {
if (this.props.navigation.isFocused()) {
// Prevent default behavior
e.preventDefault();
this.onGoHome();
}
});
// Bootstrap component state
this.bootstrapAsync();
}
componentDidUpdate(prevProps, prevState) {
if (typeof this.props.navigation.state.params.activeServer != 'undefined' &&
prevProps.navigation.state.params.activeServer !== this.props.navigation.state.params.activeServer) {
if (typeof this.props.route.params?.activeServer != 'undefined' &&
prevProps.route.params?.activeServer !== this.props.route.params?.activeServer) {
this.bootstrapAsync();
}
if (prevState.isFullscreen !== this.state.isFullscreen) {
// Update the screen orientation
this.updateScreenOrientation();
// Show/hide the bottom tab bar
this.props.navigation.setParams({
this.props.navigation.setOptions({
tabBarVisible: !this.state.isFullscreen
});
// Show/hide the status bar
@ -256,3 +266,10 @@ const styles = StyleSheet.create({
textAlign: 'center'
}
});
// Inject the Navigation Hook as a prop to mimic the legacy behavior
const HomeScreenWithNavigation = function(props) {
return <HomeScreen {...props} navigation={useNavigation()} route={useRoute()} />;
};
export default HomeScreenWithNavigation;

39
screens/LoadingScreen.js Normal file
View File

@ -0,0 +1,39 @@
/**
* 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 * as React from 'react';
import { ActivityIndicator, Image, StyleSheet, View } from 'react-native';
import Colors from '../constants/Colors';
import { SplashScreen } from 'expo';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.backgroundColor
},
splash: {
flex: 1,
resizeMode: 'contain',
width: undefined,
height: undefined
}
});
function LoadingScreen() {
return (
<View style={styles.container}>
<Image
style={styles.splash}
source={require('../assets/images/splash.png')}
onLoad={() => SplashScreen.hide()}
fadeDuration={0} // we need to adjust Android devices (https://facebook.github.io/react-native/docs/image#fadeduration) fadeDuration prop to `0` as it's default value is `300`
/>
<ActivityIndicator />
</View>
);
}
export default LoadingScreen;

View File

@ -1,68 +0,0 @@
/**
* 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 React from 'react';
import { ActivityIndicator, Image, StyleSheet, View } from 'react-native';
import { SplashScreen } from 'expo';
import Colors from '../constants/Colors';
import StorageKeys from '../constants/Storage';
import CachingStorage from '../utils/CachingStorage';
export default class ServerLoadingScreen extends React.Component {
state = { areResourcesReady: false };
constructor(props) {
super(props);
SplashScreen.preventAutoHide();
}
async getServers() {
return await CachingStorage.getInstance().getItem(StorageKeys.Servers);
}
async bootstrapAsync() {
const servers = await this.getServers();
const hasServer = !!servers && servers.length > 0;
console.info('servers', servers, hasServer);
// Ensure the splash screen is hidden
SplashScreen.hide();
// Navigate to the appropriate screen
this.props.navigation.navigate(hasServer ? 'Main' : 'AddServer');
}
componentDidMount() {
this.bootstrapAsync();
}
render() {
if (!this.state.areResourcesReady) {
return null;
}
return (
<View style={styles.container}>
<Image
style={{ flex: 1, resizeMode: 'contain', width: undefined, height: undefined }}
source={require('../assets/images/splash.png')}
onLoadEnd={() => {
// wait for image's content to fully load [`Image#onLoadEnd`] (https://facebook.github.io/react-native/docs/image#onloadend)
SplashScreen.hide();
}}
fadeDuration={0} // we need to adjust Android devices (https://facebook.github.io/react-native/docs/image#fadeduration) fadeDuration prop to `0` as it's default value is `300`
/>
<ActivityIndicator />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.backgroundColor
}
});

View File

@ -17,6 +17,8 @@ import {
import { Button, colors, ListItem, Text, Icon, Overlay } from 'react-native-elements';
import Constants from 'expo-constants';
import Url from 'url';
import { useNavigation } from '@react-navigation/native';
import PropTypes from 'prop-types';
import ServerInput from '../components/ServerInput';
import SettingSection from '../components/SettingSection';
@ -28,10 +30,10 @@ import JellyfinValidator from '../utils/JellyfinValidator';
import { getAppName } from '../utils/Device';
import { openBrowser } from '../utils/WebBrowser';
export default class SettingsScreen extends React.Component {
static navigationOptions = {
title: 'Settings'
};
class SettingsScreen extends React.Component {
static propTypes = {
navigation: PropTypes.object.isRequired
}
state = {
isAddServerVisible: false,
@ -166,7 +168,7 @@ export default class SettingsScreen extends React.Component {
// Save to storage cache
await CachingStorage.getInstance().setItem(StorageKeys.Servers, servers);
// Navigate to the loading screen
this.props.navigation.navigate('ServerLoading');
this.props.navigation.navigate('Loading');
}
async resetApplication() {
@ -175,7 +177,7 @@ export default class SettingsScreen extends React.Component {
// Reset the storage cache
CachingStorage.instance = null;
// Navigate to the loading screen
this.props.navigation.navigate('ServerLoading');
this.props.navigation.navigate('Loading');
}
onDeleteServer(index) {
@ -264,7 +266,6 @@ export default class SettingsScreen extends React.Component {
onBackdropPress={() => this.setState({ isAddServerVisible: false })}
>
<ServerInput
navigation={this.props.navigation}
onSuccess={() => {
this.setState({ isAddServerVisible: false });
this.bootstrapAsync();
@ -290,3 +291,11 @@ const styles = StyleSheet.create({
fontSize: 15
}
});
// Inject the Navigation Hook as a prop to mimic the legacy behavior
const SettingsScreenWithNavigation = function(props) {
const navigation = useNavigation();
return <SettingsScreen {...props} navigation={navigation} />;
};
export default SettingsScreenWithNavigation;

2226
yarn.lock

File diff suppressed because it is too large Load Diff