Browse Source

series ranking offline mode

Viktoriia 1 year ago
parent
commit
322d31b566

+ 2 - 0
src/database/index.ts

@@ -8,6 +8,7 @@ import { fetchAndSaveAllTypesAndMasters } from './unMastersService';
 import { updateAvatars } from './avatarsService';
 import { fetchAndSaveStatistics } from './statisticsService';
 import { saveTriumphsData } from './triumphsService';
+import { saveSeriesRankingData } from './seriesRankingService';
 
 const db = SQLite.openDatabase('nomadManiaDb.db');
 const lastUpdateDate = storage.get('lastUpdateDate', StoreType.STRING) as string || '1990-01-01';
@@ -88,6 +89,7 @@ const updateMasterRanking = async () => {
   await fetchAndSaveStatistics(token);
   await fetchAndSaveAllTypesAndMasters();
   await saveTriumphsData();
+  await saveSeriesRankingData();
 };
 
 export default setupDatabaseAndSync;

+ 30 - 0
src/database/seriesRankingService/index.ts

@@ -0,0 +1,30 @@
+import { seriesApi } from '@api/series';
+import { storage } from 'src/storage';
+
+const NAMESPACE = 'series';
+
+function saveData<T>(key: string, data: T) {
+  const namespacedKey = `${NAMESPACE}:${key}`;
+  const jsonData = JSON.stringify(data);
+  storage.set(namespacedKey, jsonData);
+}
+
+export async function saveSeriesRankingData() {
+  const response = await seriesApi.getSeriesGroupsRanking();
+  const groups = response.data.data;
+  saveData('groups', groups);
+
+  await Promise.all(
+    groups.map(async (group) => {
+      const response = await seriesApi.getSeriesRanking(group.id, 0, 50);
+      saveData(`${group.id}`, response.data.data);
+
+      if (group.series) {
+        group.series.map(async (series) => {
+          const subseries = await seriesApi.getSeriesRanking(series.id, 0, 50);
+          saveData(`${series.id}`, subseries.data.data);
+        });
+      }
+    })
+  );
+}

+ 13 - 1
src/modules/api/series/queries/use-post-get-series-groups-ranking.tsx

@@ -1,14 +1,26 @@
 import { useQuery } from '@tanstack/react-query';
 
 import { seriesQueryKeys } from '../series-query-keys';
-import { seriesApi, type PostGetSeriesGroupsRanking } from '../series-api';
+import { seriesApi, type PostGetSeriesGroupsRanking, type GroupsData } from '../series-api';
 
 import type { BaseAxiosError } from '../../../../types';
+import { useConnection } from 'src/contexts/ConnectionContext';
+import { loadData } from 'src/storage';
 
 export const useGetSeriesGroupsRanking = (enabled: boolean) => {
+  const netInfo = useConnection();
+
   return useQuery<PostGetSeriesGroupsRanking, BaseAxiosError>({
     queryKey: seriesQueryKeys.getSeriesGroupsRanking(),
     queryFn: async () => {
+      if (!netInfo?.isInternetReachable) {
+        const localData = loadData<GroupsData[]>('series', `groups`);
+        if (localData) {
+          return { data: localData } as PostGetSeriesGroupsRanking;
+        }
+        return { data: [], result: 'ERROR' } as PostGetSeriesGroupsRanking;
+      }
+
       const response = await seriesApi.getSeriesGroupsRanking();
       return response.data;
     },

+ 13 - 1
src/modules/api/series/queries/use-post-get-series-ranking.tsx

@@ -1,9 +1,11 @@
 import { useQuery } from '@tanstack/react-query';
 
 import { seriesQueryKeys } from '../series-query-keys';
-import { seriesApi, type PostGetSeriesRanking } from '../series-api';
+import { seriesApi, type PostGetSeriesRanking, type RankingData } from '../series-api';
 
 import type { BaseAxiosError } from '../../../../types';
+import { useConnection } from 'src/contexts/ConnectionContext';
+import { loadData } from 'src/storage';
 
 export const useGetSeriesRanking = (
   id: number,
@@ -11,9 +13,19 @@ export const useGetSeriesRanking = (
   page_size: number,
   enabled: boolean
 ) => {
+  const netInfo = useConnection();
+
   return useQuery<PostGetSeriesRanking, BaseAxiosError>({
     queryKey: seriesQueryKeys.getSeriesRanking(id, page, page_size),
     queryFn: async () => {
+      if (!netInfo?.isInternetReachable) {
+        const localData = loadData<RankingData>('series', `${id}`);
+        if (localData) {
+          return { data: localData, result: 'OK' } as PostGetSeriesRanking;
+        }
+        return { data: {}, result: 'ERROR' } as PostGetSeriesRanking;
+      }
+
       const response = await seriesApi.getSeriesRanking(id, page, page_size);
       return response.data;
     },

+ 32 - 28
src/modules/api/series/series-api.tsx

@@ -68,39 +68,43 @@ export interface PostSetToggleItem {
   double: 0 | 1;
 }
 
+export interface GroupsData {
+  name: string;
+  id: number;
+  series:
+    | {
+        id: number;
+        name: string;
+      }[]
+    | null;
+}
+
 export interface PostGetSeriesGroupsRanking extends ResponseType {
-  data: {
-    name: string;
-    id: number;
-    series:
-      | {
-          name: string;
-          id: number;
-        }[]
-      | null;
+  data: GroupsData[];
+}
+
+export interface RankingData {
+  max_points: number;
+  max_pages: number;
+  ranking: {
+    user_id: number;
+    score: number;
+    avatar: string | null;
+    first_name: string;
+    last_name: string;
+    age: number;
+    authenticated: 0 | 1;
+    badge_nm: 0 | 1;
+    badge_un: 0 | 1;
+    flag1: string;
+    flag2: string | null;
+    rip: boolean;
+    recent_score_increase: boolean;
   }[];
 }
 
 export interface PostGetSeriesRanking extends ResponseType {
-  data: {
-    max_points: number;
-    max_pages: number;
-    ranking: {
-      user_id: number;
-      score: number;
-      avatar: string | null;
-      first_name: string;
-      last_name: string;
-      age: number;
-      authenticated: 0 | 1;
-      badge_nm: 0 | 1;
-      badge_un: 0 | 1;
-      flag1: string;
-      flag2: string | null;
-      rip: boolean;
-      recent_score_increase: boolean;
-    }[];
-  };
+  data: RankingData;
 }
 
 export const seriesApi = {

+ 20 - 7
src/screens/InAppScreens/TravellersScreen/Components/SeriesRankingItem.tsx

@@ -1,5 +1,6 @@
 import React from 'react';
 import { Text, TouchableOpacity, View, Image } from 'react-native';
+import * as FileSystem from 'expo-file-system';
 
 import { AvatarWithInitials } from '../../../../components';
 
@@ -8,6 +9,7 @@ import { getFontSize } from 'src/utils';
 import { Colors } from 'src/theme';
 import { SeriesRanking } from '../utils/types';
 import { ProfileStyles, ScoreStyles } from './styles';
+import { useConnection } from 'src/contexts/ConnectionContext';
 
 import TickIcon from 'assets/icons/tick.svg';
 import UNIcon from 'assets/icons/un_icon.svg';
@@ -24,6 +26,17 @@ const SeriesRankingItem = React.memo(
     index: number;
     onPress: (userId: number) => void;
   }) => {
+    const netInfo = useConnection();
+
+    const avatarBaseUri = netInfo?.isInternetReachable
+      ? API_HOST + item.avatar
+      : `${FileSystem.documentDirectory}avatars/${item.user_id}.webp`;
+
+    const flagBaseUri = (flag: string) =>
+      netInfo?.isInternetReachable
+        ? API_HOST + flag
+        : `${FileSystem.documentDirectory}flags/${flag.split('/').pop()}`;
+
     return (
       <TouchableOpacity
         onPress={() => onPress(item.user_id)}
@@ -36,14 +49,11 @@ const SeriesRankingItem = React.memo(
             </View>
             <View style={{ marginLeft: index + 1 < 100 ? 5 : index + 1 < 1000 ? 15 : 25 }}>
               {item.avatar ? (
-                <Image
-                  style={ProfileStyles.profileAvatar}
-                  source={{ uri: API_HOST + item.avatar }}
-                />
+                <Image style={ProfileStyles.profileAvatar} source={{ uri: avatarBaseUri }} />
               ) : (
                 <AvatarWithInitials
                   text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
-                  flag={API_HOST + item.flag1}
+                  flag={flagBaseUri(item.flag1)}
                   size={48}
                 />
               )}
@@ -58,10 +68,13 @@ const SeriesRankingItem = React.memo(
             <View style={ProfileStyles.profileDataContainer}>
               <View style={ProfileStyles.profileDataWrapper}>
                 <Text style={ProfileStyles.profileAge}>Age: {item.age ?? ''}</Text>
-                <Image source={{ uri: API_HOST + item.flag1 }} style={ProfileStyles.countryFlag} />
+                <Image
+                  source={{ uri: flagBaseUri(item.flag1) }}
+                  style={ProfileStyles.countryFlag}
+                />
                 {item.flag2 && item.flag2 !== item.flag1 ? (
                   <Image
-                    source={{ uri: API_HOST + item.flag2 }}
+                    source={{ uri: flagBaseUri(item.flag2) }}
                     style={[ProfileStyles.countryFlag, { marginLeft: -15 }]}
                   />
                 ) : null}

+ 7 - 2
src/screens/InAppScreens/TravellersScreen/SeriesRankingListScreen/index.tsx

@@ -82,11 +82,16 @@ const SeriesRankingListScreen = ({ route }: { route: any }) => {
   );
 
   const handleEndReached = useCallback(() => {
-    if (data && data.result === 'OK' && page < data.data.max_pages && !isLoadingMore) {
+    if (
+      data &&
+      data.result === 'OK' &&
+      page < data.data.max_pages &&
+      netInfo?.isInternetReachable
+    ) {
       setIsLoadingMore(true);
       setPage((prevPage) => prevPage + 1);
     }
-  }, [data, page, isLoadingMore]);
+  }, [data, page, netInfo?.isInternetReachable]);
 
   if (loading) return <Loading />;