feat: Save last page and add library view for comics
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
0edd8778a3
commit
821f8331e5
11 changed files with 238 additions and 23 deletions
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^13.0.0",
|
"@expo/vector-icons": "^13.0.0",
|
||||||
"@expo/webpack-config": "^0.17.0",
|
"@expo/webpack-config": "^0.17.0",
|
||||||
|
"@react-native-async-storage/async-storage": "~1.17.3",
|
||||||
"@react-native-picker/picker": "2.4.2",
|
"@react-native-picker/picker": "2.4.2",
|
||||||
"@react-navigation/bottom-tabs": "^6.4.0",
|
"@react-navigation/bottom-tabs": "^6.4.0",
|
||||||
"@react-navigation/native": "^6.0.12",
|
"@react-navigation/native": "^6.0.12",
|
||||||
|
|
22
src/components/Chip.tsx
Normal file
22
src/components/Chip.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { FC, ReactNode } from "react";
|
||||||
|
import { Text, View } from "react-native";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
colour: string;
|
||||||
|
children: ReactNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chip: FC<Props> = (props) => {
|
||||||
|
return <Text style={{
|
||||||
|
backgroundColor: props.colour,
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
fontSize: 12,
|
||||||
|
height: 24,
|
||||||
|
color: 'white',
|
||||||
|
}}>
|
||||||
|
{props.children}
|
||||||
|
</Text>
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chip;
|
|
@ -1,16 +1,27 @@
|
||||||
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { Image, StyleSheet, Text, View } from "react-native";
|
import { Image, StyleSheet, Text, View } from "react-native";
|
||||||
import { ComicMetadata } from "../provider";
|
import { ComicMetadata } from "../provider";
|
||||||
|
import Chip from "./Chip";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
comic: ComicMetadata
|
comic: ComicMetadata & { isLocal?: boolean }
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComicCard: FC<Props> = (props) => {
|
const ComicCard: FC<Props> = (props) => {
|
||||||
return <View style={styles.aboutRow}>
|
return <View style={styles.aboutRow}>
|
||||||
<Image source={{ uri: props.comic.avatarUrl }} style={styles.avatar} />
|
<Image source={{ uri: props.comic.avatarUrl }} style={styles.avatar} />
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<Text style={styles.title}>{props.comic.title}</Text>
|
<View style={styles.titleRow}>
|
||||||
|
<Text style={styles.title}>
|
||||||
|
{props.comic.title}
|
||||||
|
</Text>
|
||||||
|
{props.comic.isLocal && <View style={styles.localChip}>
|
||||||
|
<Chip colour="#d602ee">
|
||||||
|
<MaterialCommunityIcons name="download" size={16} />
|
||||||
|
</Chip>
|
||||||
|
</View>}
|
||||||
|
</View>
|
||||||
<Text style={styles.subtitle}>{props.comic.shortDescription}</Text>
|
<Text style={styles.subtitle}>{props.comic.shortDescription}</Text>
|
||||||
<Text style={styles.author}>By {props.comic.author}</Text>
|
<Text style={styles.author}>By {props.comic.author}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
@ -29,9 +40,16 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
paddingLeft: 8,
|
paddingLeft: 8,
|
||||||
},
|
},
|
||||||
|
titleRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
},
|
},
|
||||||
|
localChip: {
|
||||||
|
paddingLeft: 4,
|
||||||
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
|
|
21
src/components/ComicSelector.tsx
Normal file
21
src/components/ComicSelector.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { FC } from "react"
|
||||||
|
import { StyleSheet, View } from "react-native"
|
||||||
|
import { ComicMetadata } from "../provider"
|
||||||
|
import ComicCard from "./ComicCard"
|
||||||
|
import Touchable from "./Touchable"
|
||||||
|
|
||||||
|
const ComicSelector: FC<ComicMetadata & { onClick: () => void; isLocal?: boolean }> = (props) => {
|
||||||
|
return <Touchable onPress={() => props.onClick()}>
|
||||||
|
<View style={styles.comic}>
|
||||||
|
<ComicCard comic={props} />
|
||||||
|
</View>
|
||||||
|
</Touchable>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ComicSelector;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
comic: {
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,8 +1,11 @@
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { NativeStackScreenProps } from "@react-navigation/native-stack";
|
import { NativeStackScreenProps } from "@react-navigation/native-stack";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { ActivityIndicator, Image as NativeImage, 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 { createImageProgress } from "react-native-image-progress";
|
||||||
import { Pie } from 'react-native-progress';
|
import { Pie } from 'react-native-progress';
|
||||||
|
import { useQueryClient } from "react-query";
|
||||||
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";
|
||||||
|
@ -12,6 +15,15 @@ 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);
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
AsyncStorage.setItem(`comic:${props.route.params.provider}:${props.route.params.comicId}:lastPageRead`, data.id);
|
||||||
|
queryClient.invalidateQueries(['currentPage', props.route.params.provider, props.route.params.comicId])
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
if (isLoading) return <ActivityIndicator />;
|
if (isLoading) return <ActivityIndicator />;
|
||||||
if (error) return <Text>Failed to load comic: {error.toString()}</Text>;
|
if (error) return <Text>Failed to load comic: {error.toString()}</Text>;
|
||||||
if (!data) return <Text>Whoops! We couldn't find that page :/</Text>;
|
if (!data) return <Text>Whoops! We couldn't find that page :/</Text>;
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons, Ionicons } from "@expo/vector-icons";
|
||||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||||
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
|
import Library from "./comiclist/Library";
|
||||||
import LocalComicList from "./comiclist/Local";
|
import LocalComicList from "./comiclist/Local";
|
||||||
|
|
||||||
const Tab = createBottomTabNavigator<TabParamList>();
|
const Tab = createBottomTabNavigator<TabParamList>();
|
||||||
|
|
||||||
export type TabParamList = {
|
export type TabParamList = {
|
||||||
Local: undefined;
|
Local: undefined;
|
||||||
|
Library: undefined;
|
||||||
Remote: undefined;
|
Remote: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,14 +26,14 @@ export default function ComicList() {
|
||||||
screenOptions={({ route }) => {
|
screenOptions={({ route }) => {
|
||||||
return {
|
return {
|
||||||
tabBarIcon: (({ focused, size, color }) => {
|
tabBarIcon: (({ focused, size, color }) => {
|
||||||
const iconName = route.name === 'Local'
|
if (route.name === 'Local') return <MaterialCommunityIcons name={focused ? 'download' : 'download-outline'} size={size} color={color} />
|
||||||
? (focused ? 'download' : 'download-outline')
|
if (route.name === 'Remote') return <MaterialCommunityIcons name={focused ? 'cloud' : 'cloud-outline'} size={size} color={color} />
|
||||||
: (focused ? 'cloud' : 'cloud-outline');
|
if (route.name === 'Library') return <Ionicons name={focused ? 'library' : 'library-outline'} size={size} color={color} />
|
||||||
return <MaterialCommunityIcons name={iconName} size={size} color={color} />
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Tab.Screen name="Library" component={Library} />
|
||||||
<Tab.Screen name="Local" component={LocalComicList} />
|
<Tab.Screen name="Local" component={LocalComicList} />
|
||||||
<Tab.Screen name="Remote" component={Remote} />
|
<Tab.Screen name="Remote" component={Remote} />
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
|
|
80
src/pages/comiclist/Library.tsx
Normal file
80
src/pages/comiclist/Library.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||||
|
import { BottomTabNavigationProp } from "@react-navigation/bottom-tabs";
|
||||||
|
import { CompositeNavigationProp, useNavigation } from "@react-navigation/native";
|
||||||
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { ActivityIndicator, FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||||
|
import { useQueryClient } from "react-query";
|
||||||
|
import { RootStackParamList } from "../../../App";
|
||||||
|
import ComicSelector from "../../components/ComicSelector";
|
||||||
|
import { ComicProviderKey, useLibraryComics } from "../../provider";
|
||||||
|
import { TabParamList } from "../comiclist";
|
||||||
|
|
||||||
|
export type LibraryNavigationProp = CompositeNavigationProp<
|
||||||
|
BottomTabNavigationProp<TabParamList, 'Local'>,
|
||||||
|
NativeStackNavigationProp<RootStackParamList, 'Home'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default function Library() {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const navigator = useNavigation<LibraryNavigationProp>();
|
||||||
|
const { data, error, isLoading } = useLibraryComics();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigator.setOptions({
|
||||||
|
headerRight: () => <View style={styles.iconRow}>
|
||||||
|
<View style={styles.padding}>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
client.invalidateQueries('libraryComics');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons name="refresh" size={24} style={{ color: "black" }} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
});
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
|
if (isLoading) return <ActivityIndicator />;
|
||||||
|
if (error) return <Text>Failed to load comics: {error.toString()}</Text>;
|
||||||
|
if (!data) return <Text>Whoops! We couldn't load those comics :/</Text>;
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return <View style={styles.center}>
|
||||||
|
<MaterialIcons name='sentiment-dissatisfied' size={32} color='#666' />
|
||||||
|
<Text style={styles.noComicsText}>You aren't reading any comics</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
|
||||||
|
function openComic(provider: ComicProviderKey, id: string) {
|
||||||
|
navigator.navigate('ComicProfile', {
|
||||||
|
provider,
|
||||||
|
comicId: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return <FlatList data={data} renderItem={({ item }) =>
|
||||||
|
<ComicSelector onClick={() => openComic(item.provider, item.comicId)} isLocal={item.provider === 'local'} {...item} />
|
||||||
|
} keyExtractor={item => item.provider + ':' + item.comicId} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
center: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
noComicsText: {
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
comic: {
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
iconRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
paddingEnd: 16,
|
||||||
|
},
|
||||||
|
});
|
|
@ -11,20 +11,13 @@ import { RootStackParamList } from "../../../App";
|
||||||
import { LocalComicId } from "../../provider/local";
|
import { LocalComicId } from "../../provider/local";
|
||||||
import { useQueryClient } from "react-query";
|
import { useQueryClient } from "react-query";
|
||||||
import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||||
|
import ComicSelector from "../../components/ComicSelector";
|
||||||
|
|
||||||
export type ComicListNavigationProp = CompositeNavigationProp<
|
export type ComicListNavigationProp = CompositeNavigationProp<
|
||||||
BottomTabNavigationProp<TabParamList, 'Local'>,
|
BottomTabNavigationProp<TabParamList, 'Local'>,
|
||||||
NativeStackNavigationProp<RootStackParamList, 'Home'>
|
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() {
|
export default function LocalComicList() {
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
const navigator = useNavigation<ComicListNavigationProp>();
|
const navigator = useNavigation<ComicListNavigationProp>();
|
||||||
|
@ -57,8 +50,8 @@ export default function LocalComicList() {
|
||||||
}, [client]);
|
}, [client]);
|
||||||
|
|
||||||
if (isLoading) return <ActivityIndicator />;
|
if (isLoading) return <ActivityIndicator />;
|
||||||
if (error) return <Text>Failed to load comic: {error.toString()}</Text>;
|
if (error) return <Text>Failed to load comics: {error.toString()}</Text>;
|
||||||
if (!data) return <Text>Whoops! We couldn't find that comic :/</Text>;
|
if (!data) return <Text>Whoops! We couldn't those comics :/</Text>;
|
||||||
|
|
||||||
function openComic(comicId: LocalComicId) {
|
function openComic(comicId: LocalComicId) {
|
||||||
navigation.navigate('ComicProfile', {
|
navigation.navigate('ComicProfile', {
|
||||||
|
@ -74,7 +67,7 @@ export default function LocalComicList() {
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <FlatList data={data} renderItem={({ item }) => <Comic onClick={() => openComic(item.id)} {...item} />} keyExtractor={item => item.id} />
|
return <FlatList data={data} renderItem={({ item }) => <ComicSelector onClick={() => openComic(item.id)} {...item} />} keyExtractor={item => item.id} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -86,9 +79,6 @@ const styles = StyleSheet.create({
|
||||||
noComicsText: {
|
noComicsText: {
|
||||||
color: '#666',
|
color: '#666',
|
||||||
},
|
},
|
||||||
comic: {
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
iconRow: {
|
iconRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,10 +2,11 @@ import { NativeStackScreenProps } from "@react-navigation/native-stack";
|
||||||
import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||||
import { RootStackParamList } from "../../App";
|
import { RootStackParamList } from "../../App";
|
||||||
import ComicCard from "../components/ComicCard";
|
import ComicCard from "../components/ComicCard";
|
||||||
import { useComicMetadata } from "../provider";
|
import { useComicMetadata, useCurrentPage } from "../provider";
|
||||||
import Divider from "../components/Divider";
|
import Divider from "../components/Divider";
|
||||||
import ChapterList from "../components/ChapterList";
|
import ChapterList from "../components/ChapterList";
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
|
import Touchable from "../components/Touchable";
|
||||||
|
|
||||||
export default function ComicProfile(props: Props) {
|
export default function ComicProfile(props: Props) {
|
||||||
const comicQuery = useComicMetadata(props.route.params.provider, props.route.params.comicId, (data) => {
|
const comicQuery = useComicMetadata(props.route.params.provider, props.route.params.comicId, (data) => {
|
||||||
|
@ -27,12 +28,22 @@ export default function ComicProfile(props: Props) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: currentPage } = useCurrentPage(props.route.params.provider, props.route.params.comicId);
|
||||||
|
|
||||||
if (comicQuery.isLoading) return <ActivityIndicator />;
|
if (comicQuery.isLoading) return <ActivityIndicator />;
|
||||||
if (comicQuery.error) return <Text>Failed to load comic: {comicQuery.error.toString()}</Text>;
|
if (comicQuery.error) return <Text>Failed to load comic: {comicQuery.error.toString()}</Text>;
|
||||||
if (!comicQuery.data) return <Text>Whoops! We couldn't find that comic :/</Text>;
|
if (!comicQuery.data) return <Text>Whoops! We couldn't find that comic :/</Text>;
|
||||||
|
|
||||||
return <View style={styles.container}>
|
return <View style={styles.container}>
|
||||||
<ComicCard comic={comicQuery.data} />
|
<ComicCard comic={comicQuery.data} />
|
||||||
|
{currentPage && <View>
|
||||||
|
<Touchable onPress={() => props.navigation.push('Page', {
|
||||||
|
...props.route.params,
|
||||||
|
pageId: currentPage,
|
||||||
|
})}>
|
||||||
|
<MaterialCommunityIcons name="bookmark" size={24} color="red" />
|
||||||
|
</Touchable>
|
||||||
|
</View>}
|
||||||
<Divider />
|
<Divider />
|
||||||
<ChapterList provider={props.route.params.provider} comicId={props.route.params.comicId} />
|
<ChapterList provider={props.route.params.provider} comicId={props.route.params.comicId} />
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
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";
|
||||||
|
@ -20,6 +21,8 @@ export interface ComicMetadata {
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FullComicMetadata = ComicMetadata & { comicId: string; provider: ComicProviderKey; };
|
||||||
|
|
||||||
export interface SimpleChapter {
|
export interface SimpleChapter {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -119,3 +122,39 @@ export function useLocalComics() {
|
||||||
() => COMIC_PROVIDERS['local'].listComics(),
|
() => COMIC_PROVIDERS['local'].listComics(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library comics are those that the user is currently reading and have a saved bookmark for
|
||||||
|
*/
|
||||||
|
export function useLibraryComics() {
|
||||||
|
return useQuery<FullComicMetadata[], Error>(
|
||||||
|
'libraryComics',
|
||||||
|
async () => {
|
||||||
|
const keys = await AsyncStorage.getAllKeys();
|
||||||
|
const promises: Promise<FullComicMetadata | null>[] = [];
|
||||||
|
for (const key of keys) {
|
||||||
|
const matches = /^comic:([a-z]+):([a-zA-Z0-9-_]+):lastPageRead$/.exec(key);
|
||||||
|
if (!matches) continue;
|
||||||
|
const provider = matches[1] as ComicProviderKey;
|
||||||
|
const id = matches[2];
|
||||||
|
promises.push(COMIC_PROVIDERS[provider].getComicInfo(id).then(comic => {
|
||||||
|
if (!comic) return comic;
|
||||||
|
return {
|
||||||
|
...comic,
|
||||||
|
comicId: id,
|
||||||
|
provider,
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const comics = await Promise.all(promises);
|
||||||
|
return comics.filter(comic => comic !== null) as FullComicMetadata[];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCurrentPage(provider: string, comicId: string) {
|
||||||
|
return useQuery<string | null, Error>(
|
||||||
|
['currentPage', provider, comicId],
|
||||||
|
() => AsyncStorage.getItem(`comic:${provider}:${comicId}:lastPageRead`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -1563,6 +1563,13 @@
|
||||||
mkdirp "^1.0.4"
|
mkdirp "^1.0.4"
|
||||||
rimraf "^3.0.2"
|
rimraf "^3.0.2"
|
||||||
|
|
||||||
|
"@react-native-async-storage/async-storage@~1.17.3":
|
||||||
|
version "1.17.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.17.10.tgz#8d6a4771912be8454a9e215eebd469b1b8e2e638"
|
||||||
|
integrity sha512-KrR021BmBLsA0TT1AAsfH16bHYy0MSbhdAeBAqpriak3GS1T2alFcdTUvn13p0ZW6FKRD6Bd3ryU2zhU/IYYJQ==
|
||||||
|
dependencies:
|
||||||
|
merge-options "^3.0.4"
|
||||||
|
|
||||||
"@react-native-community/cli-clean@^8.0.4":
|
"@react-native-community/cli-clean@^8.0.4":
|
||||||
version "8.0.4"
|
version "8.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz#97e16a20e207b95de12e29b03816e8f2b2c80cc7"
|
resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz#97e16a20e207b95de12e29b03816e8f2b2c80cc7"
|
||||||
|
@ -5977,6 +5984,11 @@ is-path-inside@^3.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||||
|
|
||||||
|
is-plain-obj@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
|
||||||
|
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
|
||||||
|
|
||||||
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||||
|
@ -6653,6 +6665,13 @@ merge-descriptors@1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||||
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
||||||
|
|
||||||
|
merge-options@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7"
|
||||||
|
integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==
|
||||||
|
dependencies:
|
||||||
|
is-plain-obj "^2.1.0"
|
||||||
|
|
||||||
merge-stream@^2.0.0:
|
merge-stream@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
|
|
Loading…
Reference in a new issue