feat: upgrade to react native 0.79
This was necessary because we had to use the newest iOS SDK, which had an issue with react-native, which was only fixed with the newest versions. The move to new architecture has been hellish, but all appears to work. It requires more patches, and it shows which packages are currently maintained poorly. This goes especially for react-native-track-player. We're using a fork right now, but in order to make that work, we had to switch to pnpm.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { PropsWithChildren, useEffect } from 'react';
|
||||
import React, { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import TrackPlayer, { Capability } from 'react-native-track-player';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@react-navigation/native';
|
||||
import { ColorSchemeProvider, themes, useUserOrSystemScheme } from './Colors';
|
||||
import DownloadManager from './DownloadManager';
|
||||
import AppLoading from './AppLoading';
|
||||
|
||||
const LightTheme = {
|
||||
...DefaultTheme,
|
||||
@@ -44,14 +45,14 @@ function ThemedNavigationContainer({ children }: PropsWithChildren<{}>) {
|
||||
);
|
||||
}
|
||||
|
||||
// Track whether the player has already been setup, so that we don't
|
||||
// accidentally do it twice.
|
||||
let hasSetupPlayer = false;
|
||||
export default function App(): JSX.Element | null {
|
||||
// Track whether the player has already been setup, so that we don't
|
||||
// accidentally do it twice.
|
||||
const [hasSetupPlayer, setHasSetupPlayer] = useState(false);
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
useEffect(() => {
|
||||
async function setupTrackPlayer() {
|
||||
await TrackPlayer.setupPlayer();
|
||||
await TrackPlayer.setupPlayer({ autoHandleInterruptions: true });
|
||||
await TrackPlayer.updateOptions({
|
||||
capabilities: [
|
||||
Capability.Play,
|
||||
@@ -63,13 +64,18 @@ export default function App(): JSX.Element {
|
||||
],
|
||||
progressUpdateEventInterval: 5,
|
||||
});
|
||||
setHasSetupPlayer(true);
|
||||
}
|
||||
|
||||
if (!hasSetupPlayer) {
|
||||
setupTrackPlayer();
|
||||
hasSetupPlayer = true;
|
||||
}
|
||||
}, []);
|
||||
}, [hasSetupPlayer]);
|
||||
|
||||
// GUARD: Wait for setup of the player before showing the rest of the app
|
||||
if (!hasSetupPlayer) {
|
||||
return (<AppLoading />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
|
||||
25
src/components/AppLoading.tsx
Normal file
25
src/components/AppLoading.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useColorScheme } from 'react-native';
|
||||
import styled from 'styled-components/native';
|
||||
|
||||
const Container = styled.View`
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Logo = styled.Image`
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e6e6e6;
|
||||
`;
|
||||
|
||||
export default function AppLoading() {
|
||||
const scheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<Container style={{ backgroundColor: scheme === 'dark' ? '#111' : '#fff' }}>
|
||||
<Logo source={require('../assets/icons/app-icon.png')} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -157,21 +157,28 @@ export function DefaultStylesProvider(props: DefaultStylesProviderProps) {
|
||||
return props.children(defaultStyles);
|
||||
}
|
||||
|
||||
export function ColoredBlurView(props: PropsWithChildren<BlurViewProps>) {
|
||||
export function ColoredBlurView({ children, style, ...props }: PropsWithChildren<BlurViewProps>) {
|
||||
const systemScheme = useColorScheme();
|
||||
const userScheme = useTypedSelector((state) => state.settings.colorScheme);
|
||||
const scheme = userScheme === ColorScheme.System ? systemScheme : userScheme;
|
||||
|
||||
return Platform.OS === 'ios' ? (
|
||||
<BlurView
|
||||
{...props}
|
||||
blurType={Platform.OS === 'ios' && majorPlatformVersion >= 13
|
||||
? scheme === 'dark' ? 'materialDark' : 'materialLight'
|
||||
: scheme === 'dark' ? 'extraDark' : 'xlight'
|
||||
} />
|
||||
<View style={[style, { overflow: 'hidden' }]}>
|
||||
<BlurView
|
||||
style={[ StyleSheet.absoluteFill, { overflow: 'hidden'} ]}
|
||||
{...props}
|
||||
blurType={Platform.OS === 'ios' && majorPlatformVersion >= 13
|
||||
? scheme === 'dark' ? 'materialDark' : 'materialLight'
|
||||
: scheme === 'dark' ? 'extraDark' : 'xlight'
|
||||
}
|
||||
/>
|
||||
{children}
|
||||
</View>
|
||||
) : (
|
||||
<View {...props} style={[ props.style, {
|
||||
<View {...props} style={[ style, {
|
||||
backgroundColor: scheme === 'light' ? '#f6f6f6fb' : '#333333fb',
|
||||
} ]} />
|
||||
} ]}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ const Container = styled.Pressable<{ hasIcon?: boolean }>`
|
||||
|
||||
${Platform.select({
|
||||
ios: css`padding: 12px;`,
|
||||
android: css`padding: 4px 12px;`,
|
||||
android: css`padding: 12px;`,
|
||||
})}
|
||||
|
||||
${({ hasIcon }) => hasIcon && css`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import Input from '@/components/Input';
|
||||
import { ActivityIndicator, Animated, KeyboardAvoidingView, SafeAreaView, View } from 'react-native';
|
||||
import { ActivityIndicator, Animated, KeyboardAvoidingView, Platform, SafeAreaView, View } from 'react-native';
|
||||
import styled from 'styled-components/native';
|
||||
import { useAppDispatch, useTypedSelector } from '@/store';
|
||||
import Fuse, { IFuseOptions } from 'fuse.js';
|
||||
@@ -29,6 +29,10 @@ import BaseAlbumImage from '@/screens/Music/stacks/components/AlbumImage';
|
||||
// import LocalIcon from '@/assets/icons/internal-drive.svg';
|
||||
// import SelectableFilter from './components/SelectableFilter';
|
||||
|
||||
const KEYBOARD_OFFSET = Platform.select({
|
||||
ios: 0,
|
||||
android: 72,
|
||||
});
|
||||
const SEARCH_INPUT_HEIGHT = 62;
|
||||
|
||||
const Container = styled(View)`
|
||||
@@ -264,7 +268,7 @@ export default function Search() {
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, marginBottom: offsets.bottom }}>
|
||||
<KeyboardAvoidingView behavior="height" style={{ flex: 1 }}>
|
||||
<KeyboardAvoidingView behavior="height" style={{ flex: 1 }} keyboardVerticalOffset={KEYBOARD_OFFSET}>
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{ flex: 2, }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component, createRef } from 'react';
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
||||
import { debounce } from 'lodash';
|
||||
import { AppState } from '@/store';
|
||||
@@ -49,19 +49,11 @@ type CredentialEventData = {
|
||||
type: 'jellyfin',
|
||||
} | undefined;
|
||||
|
||||
class CredentialGenerator extends Component<Props> {
|
||||
ref = createRef<WebView>();
|
||||
const CredentialGenerator: React.FC<Props> = ({ serverUrl, onCredentialsRetrieved }) => {
|
||||
const webViewRef = useRef<WebView>(null);
|
||||
|
||||
handleStateChange = () => {
|
||||
// Call a debounced version to check if the credentials are there
|
||||
this.checkIfCredentialsAreThere();
|
||||
};
|
||||
|
||||
checkIfCredentialsAreThere = debounce(() => {
|
||||
// Inject some javascript to check if the credentials can be extracted
|
||||
// from localstore. We simultaneously attempt to extract credentials for
|
||||
// any back-end.
|
||||
this.ref.current?.injectJavaScript(`
|
||||
const checkIfCredentialsAreThere = useCallback(debounce(() => {
|
||||
webViewRef.current?.injectJavaScript(`
|
||||
try {
|
||||
let credentials = JSON.parse(window.localStorage.getItem('jellyfin_credentials'));
|
||||
let deviceId = window.localStorage.getItem('_deviceId2');
|
||||
@@ -73,9 +65,9 @@ class CredentialGenerator extends Component<Props> {
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({ credentials, deviceId, type: 'emby' }))
|
||||
} catch(e) { }; true;
|
||||
`);
|
||||
}, 500);
|
||||
}, 500), []);
|
||||
|
||||
handleMessage = async (event: WebViewMessageEvent) => {
|
||||
const handleMessage = useCallback(async (event: WebViewMessageEvent) => {
|
||||
// GUARD: Something must be returned for this thing to work
|
||||
if (!event.nativeEvent.data) {
|
||||
return;
|
||||
@@ -83,6 +75,7 @@ class CredentialGenerator extends Component<Props> {
|
||||
|
||||
// Parse the content
|
||||
const data = JSON.parse(event.nativeEvent.data) as CredentialEventData;
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('Received credential event data: ', JSON.stringify(data));
|
||||
}
|
||||
@@ -143,28 +136,25 @@ class CredentialGenerator extends Component<Props> {
|
||||
}
|
||||
|
||||
// If a message is received, the credentials should be there
|
||||
this.props.onCredentialsRetrieved({
|
||||
onCredentialsRetrieved({
|
||||
uri: address,
|
||||
user_id: userId,
|
||||
access_token: accessToken,
|
||||
device_id: deviceId,
|
||||
type: data.type,
|
||||
});
|
||||
};
|
||||
}, [onCredentialsRetrieved]);
|
||||
|
||||
render() {
|
||||
const { serverUrl } = this.props;
|
||||
|
||||
return (
|
||||
<WebView
|
||||
source={{ uri: serverUrl as string }}
|
||||
onNavigationStateChange={this.handleStateChange}
|
||||
onMessage={this.handleMessage}
|
||||
ref={this.ref}
|
||||
startInLoadingState={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<WebView
|
||||
source={{ uri: serverUrl as string }}
|
||||
onNavigationStateChange={checkIfCredentialsAreThere}
|
||||
onMessage={handleMessage}
|
||||
ref={webViewRef}
|
||||
startInLoadingState={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CredentialGenerator;
|
||||
Reference in New Issue
Block a user