diff --git a/src/screens/anime/airing-schedule/screen.tsx b/src/screens/anime/airing-schedule/screen.tsx
index 84fad51..d64130e 100644
--- a/src/screens/anime/airing-schedule/screen.tsx
+++ b/src/screens/anime/airing-schedule/screen.tsx
@@ -14,6 +14,7 @@ import { useGraphQL } from '@/hooks/use-graphql';
import DetailsCard from '@/screens/search/components/details-card';
import { ActivityIndicator, Button, groupBy, Text, View } from '@/ui';
import Pressable from '@/ui/core/pressable';
+import RefreshControl from '@/ui/core/refresh-control';
import colors from '@/ui/theme/colors';
dayjs.extend(duration);
@@ -80,7 +81,12 @@ const AiringScheduleScreen = () => {
return days;
}, []);
- const { data: schedules, isLoading } = useGraphQL(document, {
+ const {
+ data: schedules,
+ isLoading,
+ isRefetching,
+ refetch,
+ } = useGraphQL(document, {
airingAt_greater,
airingAt_lesser,
});
@@ -205,7 +211,7 @@ const AiringScheduleScreen = () => {
ItemSeparatorComponent={() => }
/>
- {isLoading ? (
+ {isLoading || isRefetching ? (
{
data={Object.entries(schedulesWithTime)?.filter(([, list]) => {
return list.length > 0;
})}
+ refreshControl={
+
+ }
renderItem={({ item }) => {
const [time, list] = item;
diff --git a/src/screens/anime/details/screens/episode-screen/components/episode-container.tsx b/src/screens/anime/details/screens/episode-screen/components/episode-container.tsx
index e06c6f6..a1b8ef2 100644
--- a/src/screens/anime/details/screens/episode-screen/components/episode-container.tsx
+++ b/src/screens/anime/details/screens/episode-screen/components/episode-container.tsx
@@ -1,4 +1,5 @@
import { useAtomValue, useSetAtom } from 'jotai/react';
+import { RotateCwIcon } from 'lucide-react-native';
import React, { useEffect, useMemo } from 'react';
import { Else, If, Then } from 'react-if';
@@ -6,6 +7,7 @@ import { type FragmentType, graphql, useFragment } from '@/gql';
import { getWatchedEpisode } from '@/storage/episode';
import { currentModuleIdAtom } from '@/store';
import { ActivityIndicator, colors, Text, View } from '@/ui';
+import Pressable from '@/ui/core/pressable';
import useEpisodes from '../hooks/use-episodes';
import { episodeChunkAtom, sectionEpisodesAtom } from '../store';
@@ -37,7 +39,7 @@ const EpisodeContainer: React.FC = ({
const media = useFragment(EpisodeContainerFragment, mediaFragment);
const currentModuleId = useAtomValue(currentModuleIdAtom);
- const { data, isLoading } = useEpisodes(media);
+ const { data, isLoading, isRefetching, refetch } = useEpisodes(media);
const setSectionEpisodes = useSetAtom(sectionEpisodesAtom);
const setEpisodeChunk = useSetAtom(episodeChunkAtom);
@@ -89,12 +91,20 @@ const EpisodeContainer: React.FC = ({
+
+ {
+ refetch();
+ }}
+ >
+
+
{currentModuleId ? (
-
+
-
+
diff --git a/src/screens/anime/details/screens/episode-screen/components/episode-layout-container.tsx b/src/screens/anime/details/screens/episode-screen/components/episode-layout-container.tsx
index 4a3291f..81abb56 100644
--- a/src/screens/anime/details/screens/episode-screen/components/episode-layout-container.tsx
+++ b/src/screens/anime/details/screens/episode-screen/components/episode-layout-container.tsx
@@ -1,4 +1,5 @@
import { useNavigation } from '@react-navigation/native';
+import { FlashList } from '@shopify/flash-list';
import { useAtomValue } from 'jotai/react';
import React, { useMemo } from 'react';
@@ -71,8 +72,10 @@ const EpisodeLayoutContainer: React.FC<{
) : null}
{layoutMode === 'details' ? (
-
- {episodes.map((episode) => {
+ {
const episodeNumber = parseInt(episode.number, 10);
return (
@@ -91,23 +94,25 @@ const EpisodeLayoutContainer: React.FC<{
}
/>
);
- })}
-
+ }}
+ keyExtractor={(item) => item.id}
+ ItemSeparatorComponent={() => }
+ />
) : null}
{layoutMode === 'card' ? (
-
- {episodes.map((episode, index) => {
+ {
const episodeNumber = parseInt(episode.number, 10);
return (
{
@@ -124,8 +129,17 @@ const EpisodeLayoutContainer: React.FC<{
/>
);
- })}
-
+ }}
+ keyExtractor={(item) => item.id}
+ ItemSeparatorComponent={() => (
+
+ )}
+ estimatedItemSize={130}
+ />
) : null}
);
diff --git a/src/screens/anime/recently-watched/screen.tsx b/src/screens/anime/recently-watched/screen.tsx
index f03f050..8d98b50 100644
--- a/src/screens/anime/recently-watched/screen.tsx
+++ b/src/screens/anime/recently-watched/screen.tsx
@@ -1,11 +1,13 @@
import { FlashList } from '@shopify/flash-list';
import React from 'react';
+import { Else, If, Then } from 'react-if';
import { useFragment } from '@/gql';
import useScreenSize from '@/hooks/use-screen-size';
import { useWatched } from '@/hooks/use-watched';
import { ActivityIndicator, Text, View } from '@/ui';
import { Card, CardFragment } from '@/ui/card';
+import RefreshControl from '@/ui/core/refresh-control';
import colors from '@/ui/theme/colors';
const SPACE_BETWEEN = 32;
@@ -15,62 +17,75 @@ const LIST_PADDING = 16;
const RecentlyWatchedScreen = () => {
const { width } = useScreenSize();
- const { data, isLoading } = useWatched(0);
-
- if (isLoading)
- return (
-
-
-
- );
-
- if (!data?.length) {
- return (
-
- You haven't watched anything yet?
-
- );
- }
+ const { data, isLoading, refetch, isRefetching } = useWatched(0);
return (
-
-
+
+
Recently watched
- {
- return (
+
+
+
+
+
+
+
+
+ You haven't watched anything.
+
+
+
+
- {
+ return (
+
+
+
+ );
+ }}
+ refreshControl={
+
+ }
+ estimatedItemSize={294}
+ keyExtractor={(item) =>
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useFragment(CardFragment, item.media).id.toString()
+ }
/>
- );
- }}
- estimatedItemSize={294}
- keyExtractor={(item) =>
- // eslint-disable-next-line react-hooks/rules-of-hooks
- useFragment(CardFragment, item.media).id.toString()
- }
- />
+
+
+
);
};
diff --git a/src/screens/search/components/details-list.tsx b/src/screens/search/components/details-list.tsx
index 1695861..f761d99 100644
--- a/src/screens/search/components/details-list.tsx
+++ b/src/screens/search/components/details-list.tsx
@@ -3,6 +3,7 @@ import React from 'react';
import { type FragmentType, useFragment } from '@/gql';
import { View } from '@/ui';
+import RefreshControl from '@/ui/core/refresh-control';
import { DetailsCardFragment } from './details-card';
import DetailsCard from './details-card';
@@ -10,9 +11,16 @@ import DetailsCard from './details-card';
interface DetailsListProps {
mediaList: readonly FragmentType[];
onLoadMore: () => void;
+ refetch: () => void;
+ isRefetching: boolean;
}
-const DetailsList: React.FC = ({ mediaList, onLoadMore }) => {
+const DetailsList: React.FC = ({
+ mediaList,
+ onLoadMore,
+ isRefetching,
+ refetch,
+}) => {
return (
= ({ mediaList, onLoadMore }) => {
return media.id.toString();
}}
+ refreshControl={
+
+ }
onEndReached={onLoadMore}
onEndReachedThreshold={0.05}
ListFooterComponentStyle={{ paddingBottom: 300 }}
diff --git a/src/screens/search/components/grid-list.tsx b/src/screens/search/components/grid-list.tsx
index 1d0968a..29323ce 100644
--- a/src/screens/search/components/grid-list.tsx
+++ b/src/screens/search/components/grid-list.tsx
@@ -5,10 +5,13 @@ import { type FragmentType, useFragment } from '@/gql';
import useScreenSize from '@/hooks/use-screen-size';
import { View } from '@/ui';
import { Card, CARD_WIDTH, CardFragment } from '@/ui/card';
+import RefreshControl from '@/ui/core/refresh-control';
interface GridListProps {
mediaList: readonly FragmentType[];
onLoadMore: () => void;
+ refetch: () => void;
+ isRefetching: boolean;
}
const SPACE_BETWEEN = 32;
@@ -17,7 +20,12 @@ const LIST_PADDING = 16;
const CARD_HEIGHT = CARD_WIDTH * (3 / 2);
const CARD_TITLE_HEIGHT = 60;
-const GridList: React.FC = ({ mediaList, onLoadMore }) => {
+const GridList: React.FC = ({
+ mediaList,
+ onLoadMore,
+ isRefetching,
+ refetch,
+}) => {
const { width } = useScreenSize();
return (
@@ -44,6 +52,9 @@ const GridList: React.FC = ({ mediaList, onLoadMore }) => {
)}
+ refreshControl={
+
+ }
estimatedItemSize={294}
onEndReached={onLoadMore}
onEndReachedThreshold={0.05}
diff --git a/src/screens/search/components/layout-container.tsx b/src/screens/search/components/layout-container.tsx
index 8c222c2..eea0578 100644
--- a/src/screens/search/components/layout-container.tsx
+++ b/src/screens/search/components/layout-container.tsx
@@ -18,11 +18,15 @@ export const SearchLayoutContainerFragment = graphql(`
interface LayoutContainerProps {
mediaList: FragmentType[];
onLoadMore: () => void;
+ refetch: () => void;
+ isRefetching: boolean;
}
const LayoutContainer: React.FC = ({
mediaList: mediaListProps,
onLoadMore,
+ isRefetching,
+ refetch,
}) => {
const layout = useAtomValue(layoutAtom);
@@ -32,10 +36,24 @@ const LayoutContainer: React.FC = ({
);
if (layout === 'grid') {
- return ;
+ return (
+
+ );
}
- return ;
+ return (
+
+ );
};
export default LayoutContainer;
diff --git a/src/screens/search/screen.tsx b/src/screens/search/screen.tsx
index 5c59902..ff0303b 100644
--- a/src/screens/search/screen.tsx
+++ b/src/screens/search/screen.tsx
@@ -114,8 +114,15 @@ const SearchScreen: React.FC = ({ route: { params } }) => {
setYear,
]);
- const { data, isLoading, fetchNextPage, isFetchingNextPage, hasNextPage } =
- useSearchMedia();
+ const {
+ data,
+ isLoading,
+ fetchNextPage,
+ isFetchingNextPage,
+ hasNextPage,
+ refetch,
+ isRefetching,
+ } = useSearchMedia();
const handleLoadMore = () => {
if (isFetchingNextPage) {
@@ -181,10 +188,12 @@ const SearchScreen: React.FC = ({ route: { params } }) => {
-
+
@@ -192,7 +201,7 @@ const SearchScreen: React.FC = ({ route: { params } }) => {
-
+
diff --git a/src/ui/core/refresh-control.tsx b/src/ui/core/refresh-control.tsx
new file mode 100644
index 0000000..930883b
--- /dev/null
+++ b/src/ui/core/refresh-control.tsx
@@ -0,0 +1,20 @@
+import { styled } from 'nativewind';
+import React from 'react';
+import type { RefreshControlProps } from 'react-native';
+import { RefreshControl as RNRefreshControl } from 'react-native-gesture-handler';
+
+import colors from '../theme/colors';
+
+const SRefreshControl = styled(RNRefreshControl);
+
+const RefreshControl = (props: RefreshControlProps) => {
+ return (
+
+ );
+};
+
+export default RefreshControl;