mirror of
https://github.com/Jellify-Music/App.git
synced 2026-02-26 13:50:06 -06:00
now featuring way more context
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
@@ -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"}}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
64
components/jellyfin-api-provider.tsx
Normal file
64
components/jellyfin-api-provider.tsx
Normal 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)
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user