now featuring way more context

This commit is contained in:
Violet Caulfield
2024-10-17 06:20:21 -05:00
parent 79cd04a9eb
commit f71dfa86f1
10 changed files with 112 additions and 109 deletions

View File

@@ -8,6 +8,10 @@ import { JellifyServer } from "../../../types/JellifyServer";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { AsyncStorageKeys } from "../../../enums/async-storage-keys";
interface ServerMutationParams {
serverUrl: string,
}
export const serverMutation = async (serverUrl: string) => {
console.log("Mutating server URL");

View File

@@ -35,7 +35,7 @@ export const fetchServer : () => Promise<JellifyServer> = () => new Promise(asyn
let serverJson = await AsyncStorage.getItem(AsyncStorageKeys.ServerUrl);
if (_.isNull(serverJson)) {
if (_.isEmpty(serverJson) || _.isNull(serverJson)) {
console.warn("No stored server address exists");
return Promise.reject(new Error("No stored server address exists"));
}

View File

@@ -3,12 +3,12 @@ import { QueryKeys } from "../../enums/query-keys"
import _ from "lodash";
import { fetchCredentials, fetchServer } from "./functions/storage";
export const useCredentials = useQuery({
export const useCredentials = () => useQuery({
queryKey: [QueryKeys.Credentials],
queryFn: fetchCredentials
});
export const useServer = useQuery({
export const useServer = () => useQuery({
queryKey: [QueryKeys.ServerUrl],
queryFn: fetchServer
})

View File

@@ -1,23 +1,21 @@
import _ from "lodash"
import ServerAuthentication from "./helpers/server-authentication";
import ServerAddress from "./helpers/server-address";
import { useServer } from "../../api/queries/keychain";
import { View } from "react-native";
import { createStackNavigator } from "@react-navigation/stack";
import { useState } from "react";
import { useApiClientContext } from "../jellyfin-api-provider";
export default function Login(): React.JSX.Element {
let { isError, data } = useServer;
const { server } = useApiClientContext();
const Stack = createStackNavigator();
let [loginState, setLoginState] = useState({})
console.log("Server from context", server);
return (
<Stack.Navigator>
{
(isError || _.isUndefined(data) || _.isEmpty(data.version)) ? (
(_.isUndefined(server) || _.isEmpty(server.url)) ? (
<Stack.Screen
name="ServerAddress"
options={{title: "ServerAddress"}}

View File

@@ -1,4 +1,4 @@
import React, { useContext, useState } from "react";
import React, { useState } from "react";
import _ from "lodash";
import { Button, TextInput, useColorScheme, View } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
@@ -6,14 +6,13 @@ import { useMutation } from "@tanstack/react-query";
import { AsyncStorageKeys } from "../../../enums/async-storage-keys";
import { JellifyServer } from "../../../types/JellifyServer";
import { mutateServer, serverMutation } from "../../../api/mutators/functions/storage";
import { useServer } from "../../../api/queries/keychain";
import { LoginContext } from "../../contexts";
import { useApiClientContext } from "../../jellyfin-api-provider";
export default function ServerAddress(): React.JSX.Element {
const loginContext = useContext(LoginContext);
const loginContext = useApiClientContext();
const [serverUrl, setServerUrl] = useState(useServer.data?.url ?? "");
const [serverAddress, setServerAddress] = useState("");
const isDarkMode = useColorScheme() === 'dark';
@@ -30,12 +29,13 @@ export default function ServerAddress(): React.JSX.Element {
// TODO: Rename url to address
let jellifyServer: JellifyServer = {
url: serverUrl,
url: serverAddress,
name: publicSystemInfoResponse.data.ServerName!,
version: publicSystemInfoResponse.data.Version!,
startUpComplete: publicSystemInfoResponse.data.StartupWizardCompleted!
}
loginContext.setServer(jellifyServer);
return mutateServer(jellifyServer);
},
onError: async (error: Error) => {
@@ -48,11 +48,13 @@ export default function ServerAddress(): React.JSX.Element {
<View>
<TextInput
placeholder="Jellyfin Server Address"
onChangeText={setServerUrl}>
onChangeText={setServerAddress}>
</TextInput>
<Button
onPress={() => useServerMutation.mutate(serverUrl)}
onPress={() => {
useServerMutation.mutate(serverAddress)
}}
title="Connect"
/>

View File

@@ -1,9 +1,9 @@
import React, { useContext } from "react";
import React from "react";
import { Button, TextInput, useColorScheme, View } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useMutation } from "@tanstack/react-query";
import { AsyncStorageKeys } from "../../../enums/async-storage-keys";
import { LoginContext } from "../../contexts";
import { useApiClientContext } from "../../jellyfin-api-provider";
export default function ServerAuthentication(): React.JSX.Element {
@@ -12,11 +12,11 @@ export default function ServerAuthentication(): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const loginContext = useContext(LoginContext)
const loginContext = useApiClientContext();
const clearServer = useMutation({
mutationFn: async () => {
loginContext.loginContextFns.setKeychainFn(undefined);
loginContext.setServer(undefined)
return await AsyncStorage.setItem(AsyncStorageKeys.ServerUrl, "");
}
})

View File

@@ -1,24 +0,0 @@
import { createContext, Dispatch, SetStateAction } from "react";
import { SharedWebCredentials } from "react-native-keychain";
import { JellifyServer } from "../types/JellifyServer";
export type LoginContextFns = {
setKeychainFn: (state: SharedWebCredentials | undefined) => void,
}
type LoginContext = {
keychain: SharedWebCredentials | undefined;
loginContextFns: LoginContextFns
};
/**
* Default value and structure for login context
* https://stackoverflow.com/a/58199140
*/
const loginContextDefaultValue : LoginContext = {
keychain: undefined,
loginContextFns: {
setKeychainFn: (state: SharedWebCredentials | undefined) => {}, // noop default callback
}
}
export const LoginContext = createContext(loginContextDefaultValue);

View File

@@ -1,20 +1,13 @@
import { ActivityIndicator, SafeAreaView, Text, useColorScheme } from "react-native";
import { Colors } from "react-native/Libraries/NewAppScreen";
import { setupPlayer } from "react-native-track-player/lib/src/trackPlayer";
import _ from "lodash";
import { JellyfinApiClientContext, JellyfinApiClientProvider, useApiClientContext } from "./jellyfin-api-provider";
import React, { useContext, useEffect } from "react";
import { NavigationContainer } from "@react-navigation/native";
import Login from "./Login/component";
import Navigation from "./navigation";
import { Colors } from "react-native/Libraries/NewAppScreen";
import { setupPlayer } from "react-native-track-player/lib/src/trackPlayer";
import { useCredentials, useServer } from "../api/queries/keychain";
import _ from "lodash";
import { jellifyStyles } from "./styles";
import { useMemo, useState } from "react";
import { LoginContext } from "./contexts";
import { SharedWebCredentials } from "react-native-keychain";
import { JellifyServer } from "../types/JellifyServer";
import LoginProvider from "./providers";
import { mutateServerCredentials } from "../api/mutators/functions/storage";
import { credentials } from "../api/mutators/storage";
import { useApi } from "../api/queries";
export default function Jellify(): React.JSX.Element {
@@ -27,34 +20,26 @@ export default function Jellify(): React.JSX.Element {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
const [keychainState, setKeychain] : [SharedWebCredentials | undefined, React.Dispatch<React.SetStateAction<SharedWebCredentials | undefined>> ]= useState();
const loginContextFns = {
setKeychainFn: (state: SharedWebCredentials | undefined) => {
setKeychain(state);
}
}
const { data: api, isPending, isError, refetch } = useApi();
const apiContext = useMemo(() => ({api, isPending, isError, refetch}),
[api, isPending, isError, refetch]
);
return (
(isPending) ? (
<SafeAreaView style={jellifyStyles.container}>
<Text>Logging in</Text>
<ActivityIndicator />
</SafeAreaView>
) : (
<LoginProvider loginContextFns={loginContextFns}>
<JellyfinApiClientProvider>
{conditionalHomeRender()}
</JellyfinApiClientProvider>
);
}
function conditionalHomeRender(): React.JSX.Element {
const apiClientContext = useApiClientContext();
return (
<SafeAreaView style={jellifyStyles.container}>
{ !_.isUndefined(apiClientContext.apiClient) ? (
<Navigation />
) : (
<NavigationContainer>
<SafeAreaView style={jellifyStyles.container}>
{ (!_.isUndefined(api) && !isError) ? <Navigation /> : <Login /> }
</SafeAreaView>
<Login />
</NavigationContainer>
</LoginProvider>
)
)}
</SafeAreaView>
);
}

View File

@@ -0,0 +1,64 @@
import { Api } from '@jellyfin/sdk';
import { createContext, ReactNode, useContext, useEffect, useState } from 'react';
import { useApi } from '../api/queries';
import _ from 'lodash';
import { JellifyServer } from '../types/JellifyServer';
import { useServer } from '../api/queries/keychain';
interface JellyfinApiClientContext {
apiClient: Api | undefined;
apiClientPending: boolean;
setApiClient: React.Dispatch<React.SetStateAction<Api | undefined>>;
server: JellifyServer | undefined;
serverPending: boolean;
setServer: React.Dispatch<React.SetStateAction<JellifyServer | undefined>>;
}
const JellyfinApiClientContextInitializer = () => {
const [apiClient, setApiClient] = useState<Api | undefined>(undefined);
const [server, setServer] = useState<JellifyServer | undefined>(undefined);
const [isServerPending, setIsServerPending] = useState<boolean>(true);
const [isApiPending, setIsApiPending] = useState<boolean>(true);
const { data: api, isPending: apiPending } = useApi();
const { data: jellyfinServer, isPending: serverPending } = useServer();
useEffect(() => {
setApiClient(api);
setServer(jellyfinServer);
setIsApiPending(apiPending);
setIsServerPending(serverPending);
}, [
api,
apiPending,
jellyfinServer,
serverPending
]);
return { apiClient, setApiClient, isApiPending, server, setServer, isServerPending }
}
export const JellyfinApiClientContext =
createContext<JellyfinApiClientContext>({
apiClient: undefined,
apiClientPending: true,
setApiClient: () => {},
server: undefined,
serverPending: true,
setServer: () => {},
});
export const JellyfinApiClientProvider = ({ children }: { children: ReactNode }) => {
const { apiClient, setApiClient, isApiPending, server, setServer, isServerPending } = JellyfinApiClientContextInitializer();;
// Add your logic to check if credentials are stored and initialize the API client here.
return (
<JellyfinApiClientContext.Provider value={{ apiClient, setApiClient, apiClientPending: isApiPending, server, setServer, serverPending: isServerPending }}>
{children}
</JellyfinApiClientContext.Provider>
);
};
export const useApiClientContext = () => useContext(JellyfinApiClientContext)

View File

@@ -1,26 +0,0 @@
import React, { useMemo } from "react"
import { LoginContext, LoginContextFns } from "./contexts"
import { useCredentials } from "../api/queries/keychain"
import { QueryObserverResult } from "@tanstack/react-query"
import { SharedWebCredentials } from "react-native-keychain"
type LoginProviderProps = {
loginContextFns: LoginContextFns
}
export default function LoginProvider(props: React.PropsWithChildren<LoginProviderProps>): React.JSX.Element {
const { data: keychain, isPending } = useCredentials;
const loginContextFns = props.loginContextFns
const loginContext = useMemo(() => ({keychain, isPending, loginContextFns}),
[keychain, isPending, loginContextFns]
);
return (
<LoginContext.Provider value={loginContext}>
{props.children}
</LoginContext.Provider>
)
}