feat: start work on new home screen and other QoL changes

This commit is contained in:
Ashhhleyyy 2022-09-20 23:36:51 +01:00
parent 73423d9789
commit 0e9f53a9b9
Signed by: ash
GPG key ID: 83B789081A0878FB
9 changed files with 195 additions and 14 deletions

11
App.tsx
View file

@ -1,4 +1,4 @@
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer, NavigatorScreenParams } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClient, QueryClientProvider } from 'react-query';
@ -9,12 +9,14 @@ import Home from './src/pages/home';
import { ComicProviderKey } from './src/provider'; import { ComicProviderKey } from './src/provider';
import { NonLocalProviderKey } from './src/provider/local'; import { NonLocalProviderKey } from './src/provider/local';
import Save from './src/pages/save'; import Save from './src/pages/save';
import ComicList, { TabParamList } from './src/pages/comiclist';
const Stack = createNativeStackNavigator<RootStackParamList>(); const Stack = createNativeStackNavigator<RootStackParamList>();
const queryClient = new QueryClient(); const queryClient = new QueryClient();
export type RootStackParamList = { export type RootStackParamList = {
Home: undefined; OldHome: undefined;
Home: NavigatorScreenParams<TabParamList>;
ComicProfile: { provider: ComicProviderKey; comicId: string; }; ComicProfile: { provider: ComicProviderKey; comicId: string; };
Chapter: { provider: ComicProviderKey; comicId: string; chapterId: string; }; Chapter: { provider: ComicProviderKey; comicId: string; chapterId: string; };
Page: { provider: ComicProviderKey; comicId: string; pageId: string; }; Page: { provider: ComicProviderKey; comicId: string; pageId: string; };
@ -26,7 +28,10 @@ export default function App() {
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<NavigationContainer> <NavigationContainer>
<Stack.Navigator> <Stack.Navigator>
<Stack.Screen name="Home" component={Home} /> <Stack.Screen name="Home" component={ComicList} options={{
headerShown: false,
}} />
<Stack.Screen name="OldHome" component={Home} />
<Stack.Screen name="ComicProfile" component={ComicProfile} /> <Stack.Screen name="ComicProfile" component={ComicProfile} />
<Stack.Screen name="Chapter" component={Chapter} /> <Stack.Screen name="Chapter" component={Chapter} />
<Stack.Screen name="Page" component={Comic} /> <Stack.Screen name="Page" component={Comic} />

View file

@ -22,10 +22,14 @@
"react": "18.0.0", "react": "18.0.0",
"react-dom": "18.0.0", "react-dom": "18.0.0",
"react-native": "0.69.5", "react-native": "0.69.5",
"react-native-fast-image": "^8.6.1",
"react-native-gesture-handler": "~2.5.0", "react-native-gesture-handler": "~2.5.0",
"react-native-image-progress": "^1.2.0",
"react-native-modalize": "^2.1.1", "react-native-modalize": "^2.1.1",
"react-native-progress": "^5.0.0",
"react-native-safe-area-context": "4.3.1", "react-native-safe-area-context": "4.3.1",
"react-native-screens": "~3.15.0", "react-native-screens": "~3.15.0",
"react-native-svg": "12.3.0",
"react-native-web": "~0.18.7", "react-native-web": "~0.18.7",
"react-query": "^3.39.2" "react-query": "^3.39.2"
}, },

View file

@ -1,10 +1,14 @@
import { MaterialCommunityIcons } from "@expo/vector-icons"; import { MaterialCommunityIcons } from "@expo/vector-icons";
import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { NativeStackScreenProps } from "@react-navigation/native-stack";
import { ActivityIndicator, Image, ScrollView, StyleSheet, Text, View } from "react-native"; import { ActivityIndicator, Image as NativeImage, ScrollView, StyleSheet, Text, View } from "react-native";
import { createImageProgress } from "react-native-image-progress";
import { Pie } from 'react-native-progress';
import { RootStackParamList } from "../../App"; import { RootStackParamList } from "../../App";
import Touchable from "../components/Touchable"; import Touchable from "../components/Touchable";
import { useComicPage } from "../provider"; import { useComicPage } from "../provider";
const Image = createImageProgress(NativeImage);
export default function Comic(props: Props) { export default function Comic(props: Props) {
const { data, error, isLoading } = useComicPage(props.route.params.provider, props.route.params.comicId, props.route.params.pageId, props.navigation); const { data, error, isLoading } = useComicPage(props.route.params.provider, props.route.params.comicId, props.route.params.pageId, props.navigation);
@ -25,7 +29,7 @@ export default function Comic(props: Props) {
{data.imageSegments.map((item) => <Image style={{ {data.imageSegments.map((item) => <Image style={{
width: '100%', width: '100%',
aspectRatio: item.width / item.height, aspectRatio: item.width / item.height,
}} source={{ uri: item.url }} resizeMode='center' key={item.url} />)} }} source={{ uri: item.url }} resizeMode='center' key={item.url} indicator={Pie} />)}
<View style={styles.navigationRow}> <View style={styles.navigationRow}>
<Touchable onPress={() => newPage(data.previousPageId)} disabled={data.previousPageId === undefined}> <Touchable onPress={() => newPage(data.previousPageId)} disabled={data.previousPageId === undefined}>

56
src/pages/comiclist.tsx Normal file
View file

@ -0,0 +1,56 @@
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
import LocalComicList from "./comiclist/Local";
const Tab = createBottomTabNavigator<TabParamList>();
export type TabParamList = {
Local: undefined;
Remote: undefined;
};
function Remote() {
return (
<View>
<Text>Hello, World!</Text>
</View>
);
}
export default function ComicList() {
return (
<Tab.Navigator
screenOptions={({ navigation, route }) => {
return {
headerRight: () => (
<View style={styles.padding}>
<TouchableOpacity
onPress={() => {
navigation.push("OldHome");
}}
>
<MaterialCommunityIcons name="cog" size={24} style={{ color: "black" }} />
</TouchableOpacity>
</View>
),
tabBarIcon: (({ focused, size, color }) => {
const iconName = route.name === 'Local'
? (focused ? 'download' : 'download-outline')
: (focused ? 'cloud' : 'cloud-outline');
return <MaterialCommunityIcons name={iconName} size={size} color={color} />
})
};
}}
>
<Tab.Screen name="Local" component={LocalComicList} />
<Tab.Screen name="Remote" component={Remote} />
</Tab.Navigator>
);
}
const styles = StyleSheet.create({
padding: {
paddingEnd: 16,
},
});

View file

@ -0,0 +1,48 @@
import { FC } from "react"
import { ActivityIndicator, FlatList, StyleSheet, Text, View } from "react-native"
import Touchable from "../../components/Touchable";
import ComicCard from '../../components/ComicCard';
import { ComicMetadata, useLocalComics } from "../../provider"
import { CompositeNavigationProp, useNavigation } from "@react-navigation/native";
import { BottomTabNavigationProp } from "@react-navigation/bottom-tabs";
import { TabParamList } from "../comiclist";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { RootStackParamList } from "../../../App";
import { LocalComicId } from "../../provider/local";
export type ComicListNavigationProp = CompositeNavigationProp<
BottomTabNavigationProp<TabParamList, 'Local'>,
NativeStackNavigationProp<RootStackParamList, 'Home'>
>;
const Comic: FC<ComicMetadata & { onClick: () => void }> = (props) => {
return <Touchable onPress={() => props.onClick()}>
<View style={styles.comic}>
<ComicCard comic={props} />
</View>
</Touchable>
}
export default function LocalComicList() {
const { data, error, isLoading } = useLocalComics();
const navigation = useNavigation<ComicListNavigationProp>();
if (isLoading) return <ActivityIndicator />;
if (error) return <Text>Failed to load comic: {error.toString()}</Text>;
if (!data) return <Text>Whoops! We couldn't find that comic :/</Text>;
function openComic(comicId: LocalComicId) {
navigation.navigate('ComicProfile', {
provider: 'local',
comicId,
});
}
return <FlatList data={data} renderItem={({ item }) => <Comic onClick={() => openComic(item.id)} {...item} />} keyExtractor={item => item.id} />
}
const styles = StyleSheet.create({
comic: {
padding: 8,
},
});

View file

@ -31,10 +31,17 @@ export default function Home(props: Props) {
alert('library yeeted!'); alert('library yeeted!');
}} /> }} />
</View> </View>
<View style={styles.button}>
<Button title="New home" onPress={async () => {
props.navigation.navigate('Home', {
screen: 'Local',
});
}} />
</View>
</View> </View>
} }
type Props = NativeStackScreenProps<RootStackParamList, 'Home'>; type Props = NativeStackScreenProps<RootStackParamList, 'OldHome'>;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View file

@ -2,7 +2,7 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { RootStackParamList } from "../../App"; import { RootStackParamList } from "../../App";
import comicfuryProvider from "./comicfury"; import comicfuryProvider from "./comicfury";
import localProvider from "./local"; import localProvider, { LocalComicMetadata } from "./local";
export interface ComicProvider { export interface ComicProvider {
getComicInfo(comicId: string): Promise<ComicMetadata | null>; getComicInfo(comicId: string): Promise<ComicMetadata | null>;
@ -112,3 +112,10 @@ export function useComicPage(provider: ComicProviderKey, comicId: string, pageId
} }
); );
} }
export function useLocalComics() {
return useQuery<LocalComicMetadata[], Error>(
'localComics',
() => COMIC_PROVIDERS['local'].listComics(),
);
}

View file

@ -1,10 +1,13 @@
import { ComicProvider, ComicProviderKey, COMIC_PROVIDERS, FullPage } from "."; import { ComicMetadata, ComicProvider, ComicProviderKey, COMIC_PROVIDERS, FullPage } from ".";
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
export type NonLocalProviderKey = Exclude<ComicProviderKey, 'local'>; export type NonLocalProviderKey = Exclude<ComicProviderKey, 'local'>;
type LocalComicId = `${NonLocalProviderKey}-${string}`; export type LocalComicId = `${NonLocalProviderKey}-${string}`;
export type LocalComicMetadata = ComicMetadata & { id: LocalComicId };
type LocalProvider = ComicProvider & { type LocalProvider = ComicProvider & {
listComics(): Promise<LocalComicMetadata[]>;
downloadComicData(providerKey: NonLocalProviderKey, comicId: string, statusCallback?: (message: string) => void): Promise<LocalComicId | null>; downloadComicData(providerKey: NonLocalProviderKey, comicId: string, statusCallback?: (message: string) => void): Promise<LocalComicId | null>;
}; };
@ -19,12 +22,12 @@ async function ensureDataDirectory(path: string) {
} }
} }
function parseComicId(comicId: string): [providerKey: string, comicId: string] | null { export function parseComicId(comicId: string): [providerKey: ComicProviderKey, comicId: string] | null {
const pts = comicId.split('-', 2); const pts = comicId.split('-', 2);
if (pts.length !== 2) { if (pts.length !== 2) {
return null; return null;
} }
return pts as [string, string]; // typescript inference bad. return pts as [ComicProviderKey, string]; // typescript inference bad.
} }
async function pathExists(path: string): Promise<boolean> { async function pathExists(path: string): Promise<boolean> {
@ -43,6 +46,26 @@ async function localJson<T>(comicId: string, path: string): Promise<T | null> {
} }
const localProvider: LocalProvider = { const localProvider: LocalProvider = {
async listComics() {
const directory = dataPath('comics/');
if (!pathExists(directory)) return [];
const providers = await FileSystem.readDirectoryAsync(directory) as NonLocalProviderKey[];
const comics: LocalComicMetadata[] = [];
for (const provider of providers) {
const comicIds = await FileSystem.readDirectoryAsync(directory + '/' + provider);
for (const comicId of comicIds) {
const combinedId: LocalComicId = `${provider}-${comicId}`;
const comic = await this.getComicInfo(combinedId);
if (comic) {
comics.push({
id: combinedId,
...comic,
});
}
}
}
return comics;
},
getComicInfo(comicId) { getComicInfo(comicId) {
return localJson(comicId, 'comic.json'); return localJson(comicId, 'comic.json');
}, },

View file

@ -3721,7 +3721,7 @@ css-select@^2.0.0:
domutils "^1.7.0" domutils "^1.7.0"
nth-check "^1.0.2" nth-check "^1.0.2"
css-select@^4.1.3: css-select@^4.1.3, css-select@^4.2.1:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
@ -3751,7 +3751,7 @@ css-tree@1.0.0-alpha.37:
mdn-data "2.0.4" mdn-data "2.0.4"
source-map "^0.6.1" source-map "^0.6.1"
css-tree@^1.1.2: css-tree@^1.0.0-alpha.39, css-tree@^1.1.2:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
@ -8328,7 +8328,7 @@ prompts@^2.3.2, prompts@^2.4.0:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@^15.7.2: prop-types@^15.7.2, prop-types@^15.8.0:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -8592,6 +8592,11 @@ react-native-codegen@^0.69.2:
jscodeshift "^0.13.1" jscodeshift "^0.13.1"
nullthrows "^1.1.1" nullthrows "^1.1.1"
react-native-fast-image@^8.6.1:
version "8.6.1"
resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-8.6.1.tgz#6a3a11b8ebc7629919265d33a420a04d13c897e0"
integrity sha512-ILuP7EuakqHNzTr8ZbumhuxG4tE/wGQ66z4nEEuzc0FlqY6rYaPsnq/UD2ahoyFj6QP1WvA2RIK3odib+VcqWg==
react-native-gesture-handler@~2.5.0: react-native-gesture-handler@~2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz#61385583570ed0a45a9ed142425e35f8fe8274fb" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz#61385583570ed0a45a9ed142425e35f8fe8274fb"
@ -8608,11 +8613,25 @@ react-native-gradle-plugin@^0.0.7:
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz#96602f909745239deab7b589443f14fce5da2056" resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz#96602f909745239deab7b589443f14fce5da2056"
integrity sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g== integrity sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g==
react-native-image-progress@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/react-native-image-progress/-/react-native-image-progress-1.2.0.tgz#81ff1b33e33c541764b9e4aebfa17b1c1a108d04"
integrity sha512-RkQydqw3p0i30vHRFFhuGNT/BXWUDJtBR5/hxJbQsfI/Ziipjm5M64bpw3VQqxUcZC8HLLg+ZQtBq1mtUlWjww==
dependencies:
prop-types "^15.8.0"
react-native-modalize@^2.1.1: react-native-modalize@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/react-native-modalize/-/react-native-modalize-2.1.1.tgz#dcb67d208f4c5bdc76f45075e727be43fd008561" resolved "https://registry.yarnpkg.com/react-native-modalize/-/react-native-modalize-2.1.1.tgz#dcb67d208f4c5bdc76f45075e727be43fd008561"
integrity sha512-4/7EZWsrUqAAkkAVEnOsSdpAPQaEBewX7TvwFuzgvGDzxKpq3O58I9SnSeU8QtG/r91XYHJNaU5dAuDrcLjUaQ== integrity sha512-4/7EZWsrUqAAkkAVEnOsSdpAPQaEBewX7TvwFuzgvGDzxKpq3O58I9SnSeU8QtG/r91XYHJNaU5dAuDrcLjUaQ==
react-native-progress@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-native-progress/-/react-native-progress-5.0.0.tgz#f5ac6ceaeee27f184c660b00f29419e82a9d0ab0"
integrity sha512-KjnGIt3r9i5Kn2biOD9fXLJocf0bwxPRxOyAgXEnZTJQU2O+HyzgGFRCbM5h3izm9kKIkSc1txh8aGmMafCD9A==
dependencies:
prop-types "^15.7.2"
react-native-safe-area-context@4.3.1: react-native-safe-area-context@4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.3.1.tgz#5cf97b25b395e0d09bc1f828920cd7da0d792ade" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.3.1.tgz#5cf97b25b395e0d09bc1f828920cd7da0d792ade"
@ -8626,6 +8645,14 @@ react-native-screens@~3.15.0:
react-freeze "^1.0.0" react-freeze "^1.0.0"
warn-once "^0.1.0" warn-once "^0.1.0"
react-native-svg@12.3.0:
version "12.3.0"
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-12.3.0.tgz#40f657c5d1ee366df23f3ec8dae76fd276b86248"
integrity sha512-ESG1g1j7/WLD7X3XRFTQHVv0r6DpbHNNcdusngAODIxG88wpTWUZkhcM3A2HJTb+BbXTFDamHv7FwtRKWQ/ALg==
dependencies:
css-select "^4.2.1"
css-tree "^1.0.0-alpha.39"
react-native-web@~0.18.7: react-native-web@~0.18.7:
version "0.18.8" version "0.18.8"
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.18.8.tgz#6a761cc5531dfbb8fffa9c1e342f153a792dce07" resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.18.8.tgz#6a761cc5531dfbb8fffa9c1e342f153a792dce07"