Browse Source

Merge branch 'statistics' of SashaGoncharov19/nomadmania-app into dev

Viktoriia 1 year ago
parent
commit
fea4b3658c

+ 7 - 0
Route.tsx

@@ -27,6 +27,7 @@ import InMemoriamScreen from './src/screens/InAppScreens/TravellersScreen/InMemo
 import InHistoryScreen from './src/screens/InAppScreens/TravellersScreen/InHistoryScreen';
 import UNMastersScreen from './src/screens/InAppScreens/TravellersScreen/UNMasters';
 import StatisticsScreen from './src/screens/InAppScreens/TravellersScreen/StatisticsScreen';
+import StatisticsListScreen from './src/screens/InAppScreens/TravellersScreen/StatisticsListScreen';
 
 import SeriesScreen from 'src/screens/InAppScreens/TravelsScreen/Series';
 import { SeriesItemScreen } from 'src/screens/InAppScreens/TravelsScreen/SeriesItemScreen';
@@ -54,6 +55,8 @@ const BottomTab = createBottomTabNavigator();
 
 SplashScreen.preventAutoHideAsync();
 
+//TODO: Fix types on Statistics
+
 const Route = () => {
   const token = storage.get('token', StoreType.STRING);
 
@@ -158,6 +161,10 @@ const Route = () => {
                     name={NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW}
                     component={ProfileScreen}
                   />
+                  <ScreenStack.Screen
+                    name={NAVIGATION_PAGES.STATISTICS_LIST_DATA}
+                    component={StatisticsListScreen}
+                  />
                 </ScreenStack.Navigator>
               )}
             </BottomTab.Screen>

+ 1 - 1
assets/icons/mark-to-up.svg

@@ -1,3 +1,3 @@
 <svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M1 1.7074L4.29293 5.00033C4.68182 5.38921 5.31818 5.38921 5.70707 5.00033L9 1.7074" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M1 1.7074L4.29293 5.00033C4.68182 5.38921 5.31818 5.38921 5.70707 5.00033L9 1.7074" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
 </svg>

+ 4 - 2
src/components/HorizontalTabView/index.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Text, View } from 'react-native';
+import { Text, TouchableOpacity, View } from 'react-native';
 import { Route, TabBar, TabView } from 'react-native-tab-view';
 
 import { styles } from './styles';
@@ -12,13 +12,15 @@ export const HorizontalTabView = ({
   setIndex,
   routes,
   renderScene,
-  withMark
+  withMark,
+  onDoubleClick
 }: {
   index: number;
   setIndex: React.Dispatch<React.SetStateAction<number>>;
   routes: Route[];
   renderScene: (props: any) => React.ReactNode;
   withMark?: boolean;
+  onDoubleClick?: () => void;
 }) => {
   const renderTabBar = (props: any) => (
     <TabBar

+ 96 - 48
src/database/statisticsService/index.ts

@@ -17,43 +17,7 @@ function loadData<T>(key: string): T | null {
 }
 
 function structureList(listData: Sublist[]) {
-  const structuredData: StructuredData = {};
-  const baseCategoryName = 'NOMADS STATISTICS';
-  const list = [];
-
-  listData.forEach((category) => {
-    const categoryName = category.name.split('<br/>')[0];
-
-    if (categoryName === baseCategoryName) {
-      if (!structuredData[baseCategoryName]) {
-        structuredData[baseCategoryName] = {
-          name: baseCategoryName,
-          sublists: []
-        };
-      }
-
-      const descriptor = category.name.split('<br/>')[1].replace(/<[^>]*>?/gm, '');
-
-      const sublist: Sublist | null =
-        structuredData[baseCategoryName].sublists?.find((el) => el.name === descriptor) || null;
-
-      if (!sublist) {
-        structuredData[baseCategoryName].sublists?.push({
-          name: descriptor,
-          list: category.list.map((item) => ({
-            name: item.name,
-            url1: item.url1,
-            url2: item.url2 || null
-          }))
-        });
-      }
-    } else {
-      list.push(category);
-    }
-  });
-
-  structuredData[baseCategoryName] && list.push(structuredData[baseCategoryName]);
-  saveData('list', list);
+  saveData('list', listData);
 }
 
 export async function fetchAndSaveStatistics(token: string) {
@@ -85,7 +49,7 @@ export function getList(): List[] | null {
   return loadData<List[]>('list');
 }
 
-interface ListData {
+export interface ListData {
   name: string;
   url1: string;
   url2?: string | null;
@@ -106,39 +70,123 @@ interface StructuredData {
   [key: string]: List;
 }
 
-interface Statistic {
+export type ListTypesArray =
+  | Type1[]
+  | Type2[]
+  | Type3[]
+  | Type4[]
+  | Type5[]
+  | Type6[]
+  | Type7[]
+  | Type8[]
+  | Type9[];
+
+export type ListType = Type1 | Type2 | Type3 | Type4 | Type5 | Type6 | Type7 | Type8 | Type9;
+
+export interface Statistic {
   data: {
     url1: string;
     url2: string;
     type: number;
     name: string;
     comment: string;
-    ranking: Type1[] | Type2[] | Type3[];
+    ranking: ListTypesArray;
   }[];
 }
 
-interface Type1 {
-  region_id: number;
+export type StatisticType = {
+  url1: string;
+  url2: string;
+  type: number;
+  name: string;
+  comment: string;
+  ranking: ListTypesArray;
+};
+
+export interface Type1 {
   cnt: number;
-  region_name: string;
   country: string;
   flag: string;
+  region_id: number;
+  region_name: string;
 }
 
-interface Type2 {
+export interface Type2 {
   cnt: number;
-  mega_id: number;
-  mega_name: string;
-  dare_name: string;
   dare_flag: string;
   dare_id: number;
+  dare_name: string;
+  mega_id: number;
+  mega_name: string;
 }
 
-interface Type3 {
+export interface Type3 {
   year: number;
   user: number;
   cnt: number;
   first_name: string;
   last_name: string;
   flag: string;
+  score?: string;
+  nation?: string;
+  user_count?: number;
+  visited_regions?: number;
+}
+
+export interface Type4 {
+  cnt: number;
+  country: string;
+  flag: string;
+  mega_id: number;
+  megaregion: string;
+  region_id: number;
+  region_name: string;
+}
+
+export interface Type5 {
+  cnt: number;
+  flag: string;
+  region_id: number;
+  region_name: string;
+}
+
+export interface Type6 {
+  cnt: number;
+  country: string;
+  country_flag: string;
+  region: string;
+  region_flag: string;
+  region_id: number;
+}
+
+export interface Type7 {
+  cnt: number;
+  country_name: string;
+  flag: string;
+}
+
+export interface Type8 {
+  cnt: number;
+  country: string;
+  flag: string;
+  mega_id: number;
+  mega_name: string;
+}
+
+export interface Type9 {
+  flag: string;
+  nation: string;
+  score: string;
+  user_count: number;
+  visited_countries: number;
+}
+
+export interface Type10 {
+  cnt: number;
+  flags: string[];
+  item_id: number;
+  item_name: string;
+  series_icon: string;
+  series_id: number;
+  series_name: string;
 }

+ 2 - 10
src/database/unMastersService/index.ts

@@ -38,10 +38,6 @@ function saveSort(filtersData: any, filterType: 'yearOfCompletion' | 'countryOfO
     const key = filterType === 'yearOfCompletion' ? item.year : item.country;
     obj[key] = item.masters.map((master: Master) => master.id);
 
-    if (filterType === 'countryOfOrigin') {
-      obj['flag'] = item.flag;
-    }
-
     return obj;
   }, {});
   saveData<SortData>(filterType, filters);
@@ -89,12 +85,9 @@ export function getMastersByCountryOfOrigin() {
   const countrySortData = loadData('countryOfOrigin');
   if (!countrySortData) return null;
 
-  console.log(countrySortData);
-
-  const countryBlocks: { country: string; count: number; masters: Master[]; flag: string }[] = [];
+  const countryBlocks: { country: string; count: number; masters: Master[] }[] = [];
 
   for (const [country, masterIds] of Object.entries(countrySortData)) {
-    if (country === 'flag') continue;
     const mastersList: Master[] = [];
     masterIds.forEach((id: number) => {
       const master = getMasterById(id);
@@ -106,8 +99,7 @@ export function getMastersByCountryOfOrigin() {
     countryBlocks.push({
       country,
       count: mastersList.length,
-      masters: mastersList,
-      flag: countrySortData.flag
+      masters: mastersList
     });
   }
 

+ 46 - 0
src/screens/InAppScreens/TravellersScreen/Components/SeriesItem.tsx

@@ -0,0 +1,46 @@
+import React, { FC } from 'react';
+import { Text, View } from 'react-native';
+import { Image } from 'expo-image';
+
+import { getFontSize } from '../../../../utils';
+import { API_HOST } from '../../../../constants';
+
+import { Colors } from '../../../../theme';
+
+import { ItemStyles } from './styles';
+
+type Props = {
+  name: string;
+  flags: string;
+  cnt: number | string;
+  index: number;
+};
+
+export const SeriesItem: FC<Props> = (data) => {
+  const flags: string[] = JSON.parse(data.flags);
+
+  return (
+    <View style={ItemStyles.wrapper}>
+      <Text style={{ color: Colors.DARK_BLUE, fontSize: getFontSize(18), fontWeight: '700' }}>
+        {data.index + 1}
+      </Text>
+      <View style={ItemStyles.contentWrapper}>
+        <View style={{ width: '85%', gap: 4 }}>
+          {flags && (
+            <View style={ItemStyles.withFlagWrapper}>
+              {flags.map((flag, index) => (
+                <Image
+                  key={index}
+                  style={[ItemStyles.smallFlag, index !== 0 && { marginLeft: -5 }]}
+                  source={{ uri: API_HOST + '/img/flags_new/' + flag }}
+                />
+              ))}
+            </View>
+          )}
+          <Text style={ItemStyles.nameAndCnt}>{data.name}</Text>
+        </View>
+        <Text style={ItemStyles.nameAndCnt}>{data.cnt}</Text>
+      </View>
+    </View>
+  );
+};

+ 26 - 0
src/screens/InAppScreens/TravellersScreen/Components/StatisticItem.tsx

@@ -0,0 +1,26 @@
+import React, { FC } from 'react';
+import { Text, View } from 'react-native';
+import { Image } from 'expo-image';
+
+import { getFontSize } from '../../../../utils';
+import { API_HOST } from '../../../../constants';
+
+import { ItemStyles } from './styles';
+
+type Props = {
+  name: string;
+  flag: string;
+  cnt: number | string;
+  index: number;
+};
+
+export const StatisticItem: FC<Props> = (data) => (
+  <View style={ItemStyles.wrapper}>
+    <Text style={[ItemStyles.nameAndCnt, { fontSize: getFontSize(18) }]}>{data.index + 1}</Text>
+    <Image style={ItemStyles.bigFlag} source={{ uri: API_HOST + '/img/flags_new/' + data.flag }} />
+    <View style={ItemStyles.contentWrapper}>
+      <Text style={[{ width: '75%' }, ItemStyles.nameAndCnt]}>{data.name}</Text>
+      <Text style={ItemStyles.nameAndCnt}>{data.cnt}</Text>
+    </View>
+  </View>
+);

+ 151 - 0
src/screens/InAppScreens/TravellersScreen/Components/StatisticRouter.tsx

@@ -0,0 +1,151 @@
+import React, { FC, useEffect } from 'react';
+import { Image } from 'expo-image';
+import { Text, View } from 'react-native';
+
+import { StatisticItem } from './StatisticItem';
+import { SeriesItem } from './SeriesItem';
+
+import { ListType } from '../../../../database/statisticsService';
+import {
+  isType1,
+  isType2,
+  isType4,
+  isType5,
+  isType6,
+  isType7,
+  isType8,
+  isType9,
+  isType10
+} from '../StatisticsListScreen/funcs';
+import { getFontSize } from '../../../../utils';
+import { API_HOST } from '../../../../constants';
+
+import { ItemStyles } from './styles';
+
+type Props = {
+  blockIndex: number;
+  item: ListType;
+  ranking: any;
+  idx: number;
+};
+
+export const RenderItem: FC<Props> = ({ item, blockIndex, ranking, idx }) => {
+  if (isType1(item) || isType4(item) || isType5(item)) {
+    return (
+      <>
+        {'megaregion' in item && (
+          <View>
+            {item.megaregion !== ranking[idx - 1]?.megaregion ? (
+              <Text style={[ItemStyles.nameAndCnt, ItemStyles.headerWrapper]}>
+                {item.megaregion as string}
+              </Text>
+            ) : null}
+          </View>
+        )}
+        <StatisticItem cnt={item.cnt} flag={item.flag} index={blockIndex} name={item.region_name} />
+      </>
+    );
+  } else if (isType6(item)) {
+    return (
+      <>
+        <View>
+          {item.country !== ranking[idx - 1]?.country ? (
+            <View style={ItemStyles.headerContainer}>
+              <Image
+                style={ItemStyles.flagSquare}
+                source={{ uri: API_HOST + '/img/flags_new/' + item.country_flag }}
+              />
+              <Text
+                style={[
+                  ItemStyles.nameAndCnt,
+                  { fontSize: getFontSize(14), textAlign: 'center', textTransform: 'uppercase' }
+                ]}
+              >
+                {item.country}
+              </Text>
+            </View>
+          ) : null}
+        </View>
+        <StatisticItem
+          cnt={item.cnt}
+          flag={item.region_flag}
+          index={blockIndex}
+          name={item.region}
+        />
+      </>
+    );
+  } else if (isType2(item)) {
+    return (
+      <>
+        <View>
+          {item.mega_name !== ranking[idx - 1]?.mega_name ? (
+            <Text style={[ItemStyles.nameAndCnt, ItemStyles.headerWrapper]}>
+              {item.mega_name as string}
+            </Text>
+          ) : null}
+        </View>
+        <StatisticItem
+          name={item.dare_name}
+          flag={item.dare_flag}
+          cnt={item.cnt}
+          index={blockIndex}
+        />
+      </>
+    );
+  } else if (isType7(item)) {
+    return (
+      <StatisticItem name={item.country_name} flag={item.flag} cnt={item.cnt} index={blockIndex} />
+    );
+  } else if (isType8(item)) {
+    return (
+      <>
+        <View>
+          {item.mega_name !== ranking[idx - 1]?.mega_name ? (
+            <Text style={[ItemStyles.nameAndCnt, ItemStyles.headerWrapper]}>
+              {item.mega_name as string}
+            </Text>
+          ) : null}
+          <StatisticItem name={item.country} flag={item.flag} cnt={item.cnt} index={blockIndex} />
+        </View>
+      </>
+    );
+  } else if (isType9(item)) {
+    return (
+      <StatisticItem name={item.nation} flag={item.flag} cnt={item.score} index={blockIndex} />
+    );
+  } else if (isType10(item)) {
+    return (
+      <>
+        {item.series_name !== ranking[idx - 1]?.series_name ? (
+          <View style={ItemStyles.headerSeriesWrapper}>
+            <Image
+              key={idx}
+              style={{ width: 20, height: 20, borderRadius: 10 }}
+              source={{ uri: API_HOST + '/static/img/series/' + item.series_icon }}
+            />
+            <Text style={[ItemStyles.nameAndCnt, { fontSize: getFontSize(14) }]}>
+              {item.series_name as string}
+            </Text>
+          </View>
+        ) : null}
+        <SeriesItem
+          name={item.item_name}
+          flags={item.flags as unknown as string}
+          cnt={item.cnt}
+          index={blockIndex}
+        />
+      </>
+    );
+  } else {
+    return (
+      <>
+        <StatisticItem
+          name={item.nation ? item.nation : `${item.first_name} ${item.last_name}`}
+          flag={item.flag}
+          cnt={item.score ? item.score : item.cnt}
+          index={blockIndex}
+        />
+      </>
+    );
+  }
+};

+ 72 - 0
src/screens/InAppScreens/TravellersScreen/Components/styles.ts

@@ -204,3 +204,75 @@ export const ModalStyles = StyleSheet.create({
     marginTop: 15
   }
 });
+
+export const ItemStyles = StyleSheet.create({
+  wrapper: {
+    display: 'flex',
+    alignItems: 'center',
+    flexDirection: 'row',
+    flex: 1,
+    gap: 8
+  },
+  contentWrapper: {
+    flex: 1,
+    display: 'flex',
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+  },
+  bigFlag: {
+    width: 36,
+    height: 36,
+    borderRadius: 18,
+    borderWidth: 1,
+    borderColor: Colors.DARK_LIGHT
+  },
+  withFlagWrapper: {
+    display: 'flex',
+    flexDirection: 'row'
+  },
+  smallFlag: {
+    width: 20,
+    height: 20,
+    borderRadius: 10,
+    borderWidth: 1,
+    borderColor: Colors.DARK_LIGHT
+  },
+  flagSquare: {
+    width: 20,
+    height: 20,
+    resizeMode: 'contain'
+  },
+  headerContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 4,
+    justifyContent: 'center',
+    paddingBottom: 16,
+    paddingTop: 8
+  },
+  nameAndCnt: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '700'
+  },
+  headerWrapper: {
+    fontSize: getFontSize(14),
+    paddingBottom: 16,
+    paddingTop: 8,
+    textAlign: 'center'
+  },
+  headerSeriesWrapper: {
+    display: 'flex',
+    justifyContent: 'center',
+    alignItems: 'center',
+    flexDirection: 'row',
+    gap: 10,
+    marginTop: 10,
+    marginBottom: 16
+  },
+  comment: {
+    color: Colors.DARK_BLUE,
+    textAlign: 'center',
+    marginBottom: 8,
+  }
+});

+ 159 - 0
src/screens/InAppScreens/TravellersScreen/StatisticsListScreen/funcs.ts

@@ -0,0 +1,159 @@
+import {
+  Type1,
+  Type10,
+  Type2,
+  Type3,
+  Type4,
+  Type5,
+  Type6,
+  Type7,
+  Type8,
+  Type9
+} from 'src/database/statisticsService';
+
+export function isType1(obj: any): obj is Type1 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 5) {
+    return false;
+  }
+
+  return (
+    'cnt' in obj && 'country' in obj && 'flag' in obj && 'region_id' in obj && 'region_name' in obj
+  );
+}
+
+export function isType2(obj: any): obj is Type2 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 6) {
+    return false;
+  }
+
+  return (
+    'cnt' in obj &&
+    'dare_flag' in obj &&
+    'dare_id' in obj &&
+    'dare_name' in obj &&
+    'mega_id' in obj &&
+    'mega_name' in obj
+  );
+}
+
+export function isType3(obj: any): obj is Type3 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 6) {
+    return false;
+  }
+
+  return (
+    'year' in obj &&
+    'user' in obj &&
+    'cnt' in obj &&
+    'first_name' in obj &&
+    'last_name' in obj &&
+    'flag' in obj
+  );
+}
+
+export function isType4(obj: any): obj is Type4 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 7) {
+    return false;
+  }
+
+  return (
+    'cnt' in obj &&
+    'country' in obj &&
+    'flag' in obj &&
+    'mega_id' in obj &&
+    'megaregion' in obj &&
+    'region_id' in obj &&
+    'region_name' in obj
+  );
+}
+
+export function isType5(obj: any): obj is Type5 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 4) {
+    return false;
+  }
+
+  return 'cnt' in obj && 'flag' in obj && 'region_id' in obj && 'region_name' in obj;
+}
+
+export function isType6(obj: any): obj is Type6 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 6) {
+    return false;
+  }
+
+  return (
+    'cnt' in obj &&
+    'country' in obj &&
+    'country_flag' in obj &&
+    'region' in obj &&
+    'region_flag' in obj &&
+    'region_id' in obj
+  );
+}
+
+export function isType7(obj: any): obj is Type7 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 3) {
+    return false;
+  }
+
+  return 'cnt' in obj && 'country_name' in obj && 'flag' in obj;
+}
+
+export function isType8(obj: any): obj is Type8 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 5) {
+    return false;
+  }
+
+  return (
+    'cnt' in obj && 'country' in obj && 'flag' in obj && 'mega_id' in obj && 'mega_name' in obj
+  );
+}
+
+export function isType9(obj: any): obj is Type9 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 5) {
+    return false;
+  }
+
+  return (
+    'flag' in obj &&
+    'nation' in obj &&
+    'score' in obj &&
+    'user_count' in obj &&
+    'visited_countries' in obj
+  );
+}
+
+export function isType10(obj: any): obj is Type10 {
+  const length = Object.keys(obj).length;
+
+  if (length !== 7) {
+    return false;
+  }
+
+  return (
+    'cnt' in obj &&
+    'flags' in obj &&
+    'item_id' in obj &&
+    'item_name' in obj &&
+    'series_icon' in obj &&
+    'series_id' in obj &&
+    'series_name' in obj
+  );
+}

+ 107 - 0
src/screens/InAppScreens/TravellersScreen/StatisticsListScreen/index.tsx

@@ -0,0 +1,107 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { FlatList, Text } from 'react-native';
+import { useFocusEffect } from '@react-navigation/native';
+
+import { RenderItem } from '../Components/StatisticRouter';
+
+import { Header, Loading, PageWrapper } from '../../../../components';
+import { getStatistic, StatisticType } from '../../../../database/statisticsService';
+import { isType1, isType5, isType7 } from '../StatisticsListScreen/funcs';
+
+import { ItemStyles } from '../Components/styles';
+
+const StatisticsListScreen = ({ route }: { route: any }) => {
+  const title = route.params.title;
+  const url1 = route.params.url1;
+  const url2 = route.params.url2;
+
+  const [isLoading, setIsLoading] = useState(true);
+  const [statistic, setStatistic] = useState<StatisticType | null>(null);
+  const [blockIndexes, setBlockIndexes] = useState<{ [key: number]: number }>({});
+
+  useFocusEffect(
+    useCallback(() => {
+      if (!statistic) return;
+      setIsLoading(false);
+    }, [statistic])
+  );
+
+  useEffect(() => {
+    function fetchStatistic() {
+      const data = getStatistic(url1, url2);
+
+      if (!data) return;
+
+      setStatistic(JSON.parse(data as unknown as string) as unknown as StatisticType);
+    }
+
+    fetchStatistic();
+  }, [url1]);
+
+  const calculateBlockIndexes = (ranking: any) => {
+    let indexes: { [key: number]: number } = {};
+    let currentBlockIdentifier = '';
+    let indexInBlock = isType1(ranking[0]) || isType5(ranking[0]) || isType7(ranking[0]) ? -1 : 0;
+
+    ranking.forEach((item: any, index: number) => {
+      const blockIdentifier = item.megaregion || item.mega_name || item.series_name || item.country;
+
+      if (isType1(item) || isType5(item) || isType7(item)) {
+        indexInBlock++;
+      } else if (blockIdentifier !== currentBlockIdentifier) {
+        currentBlockIdentifier = blockIdentifier;
+        indexInBlock = 0;
+      } else {
+        indexInBlock++;
+      }
+
+      indexes[index] = indexInBlock;
+    });
+
+    return indexes;
+  };
+
+  useEffect(() => {
+    if (statistic?.ranking) {
+      setBlockIndexes(calculateBlockIndexes(statistic.ranking));
+    }
+  }, [statistic]);
+
+  if (isLoading) return <Loading />;
+  if (!statistic) return null;
+
+  return (
+    <PageWrapper style={{ flex: 1 }}>
+      <Header label={title} />
+      {statistic.ranking ? (
+        <FlatList
+          contentContainerStyle={{ gap: 20, paddingBottom: 16 }}
+          style={{ flex: 1 }}
+          horizontal={false}
+          data={statistic.ranking as any}
+          keyExtractor={(item, index) => index.toString()}
+          initialNumToRender={20}
+          renderItem={({ item, index }) => {
+            const blockIndex: number = blockIndexes[index];
+            return (
+              <RenderItem
+                item={item}
+                blockIndex={blockIndex}
+                idx={index}
+                ranking={statistic?.ranking}
+              />
+            );
+          }}
+          showsVerticalScrollIndicator={false}
+          ListHeaderComponent={
+            statistic.comment ? (
+              <Text style={ItemStyles.comment}>{statistic.comment.replaceAll('<br/>', '\n')}</Text>
+            ) : null
+          }
+        />
+      ) : null}
+    </PageWrapper>
+  );
+};
+
+export default StatisticsListScreen;

+ 84 - 13
src/screens/InAppScreens/TravellersScreen/StatisticsScreen/index.tsx

@@ -1,12 +1,20 @@
 import React, { useEffect, useState } from 'react';
-import { getList } from '../../../../database/statisticsService';
-import { Header, HorizontalTabView, Loading, PageWrapper } from '../../../../components';
+import { getList, ListData } from '../../../../database/statisticsService';
+import { Header, Loading, PageWrapper } from '../../../../components';
+
+import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import { NAVIGATION_PAGES } from '../../../../types';
+import ArrowIcon from '../../../../../assets/icons/mark-to-up.svg';
+import { styles } from './styles';
 
 const StatisticsScreen = () => {
-  const [index, setIndex] = useState(0);
-  const [routes, setRoutes] = useState<{ key: string; title: string }[]>([]);
+  const [index, setIndex] = useState<number | null>(null);
+  const [routes, setRoutes] = useState<{ key: string; title: string; list?: ListData[] }[]>([]);
   const [loading, setLoading] = useState(true);
 
+  const navigation = useNavigation();
+
   useEffect(() => {
     const types = getList();
 
@@ -26,17 +34,80 @@ const StatisticsScreen = () => {
   return (
     <>
       <PageWrapper>
-        <Header label={'Statistics'} />
-        <HorizontalTabView
-          index={index}
-          setIndex={setIndex}
-          withMark={true}
-          routes={routes}
-          renderScene={({ route }: { route: { key: string; title: string } }) => <></>}
-        />
+        <ScrollView showsVerticalScrollIndicator={false}>
+          <Header label={'Statistics'} />
+          <View style={{ gap: 8 }}>
+            {routes.map((route, i) => {
+              let name = route.title;
+              let subname: string = '';
+
+              if (route.title.startsWith('NOMADS STATISTICS')) {
+                name = route.title.replace(/<[^>]*>/g, '!');
+
+                const split = name.split('!');
+
+                name = split[0];
+                subname = split[2];
+              }
+
+              return (
+                <View key={i}>
+                  <TouchableOpacity
+                    onPress={() => setIndex(index === i ? null : i)}
+                    style={styles.itemContainer}
+                  >
+                    <View>
+                      <Text style={styles.title}>{route.title && name}</Text>
+                      {subname && <Text style={styles.subtitle}>{subname}</Text>}
+                    </View>
+                    <View style={index === i ? { transform: 'rotate(180deg)' } : {}}>
+                      <ArrowIcon height={18} width={18} stroke={'#B7C6CB'} />
+                    </View>
+                  </TouchableOpacity>
+                  {index === i && (
+                    <View>
+                      {route.list?.map((item, index) => {
+                        return (
+                          <TouchableOpacity
+                            key={index}
+                            onPress={() =>
+                              navigation.navigate(
+                                ...([
+                                  NAVIGATION_PAGES.STATISTICS_LIST_DATA,
+                                  {
+                                    title: item.name,
+                                    type: route.title,
+                                    url1: item.url1,
+                                    url2: item.url2
+                                  }
+                                ] as never)
+                              )
+                            }
+                            style={styles.listItem}
+                          >
+                            <Text style={styles.subtitle}>• </Text>
+                            <Text
+                              style={[
+                                styles.subtitle,
+                                {
+                                  flexShrink: 1
+                                }
+                              ]}
+                            >
+                              {item.name}
+                            </Text>
+                          </TouchableOpacity>
+                        );
+                      })}
+                    </View>
+                  )}
+                </View>
+              );
+            })}
+          </View>
+        </ScrollView>
       </PageWrapper>
     </>
   );
 };
-
 export default StatisticsScreen;

+ 25 - 0
src/screens/InAppScreens/TravellersScreen/StatisticsScreen/styles.tsx

@@ -0,0 +1,25 @@
+import { Colors } from '../../../../theme';
+import { StyleSheet } from 'react-native';
+import { getFontSize } from '../../../../utils';
+
+export const styles = StyleSheet.create({
+  itemContainer: {
+    display: 'flex',
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    paddingVertical: 8,
+    paddingRight: 12
+  },
+  title: {
+    fontSize: getFontSize(16),
+    color: 'rgba(237, 147, 52, 1)',
+    fontWeight: '700'
+  },
+  subtitle: { color: Colors.DARK_BLUE, fontWeight: '700' },
+  listItem: {
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    flexDirection: 'row'
+  }
+});

+ 3 - 13
src/screens/InAppScreens/TravellersScreen/UNMasters/index.tsx

@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import { FlatList, Text, TouchableOpacity, View } from 'react-native';
+import { FlatList, Text, View } from 'react-native';
 import { Image } from 'expo-image';
 
 import { Header, HorizontalTabView, Loading, PageWrapper } from '../../../../components';
@@ -13,8 +13,6 @@ import { API_HOST } from '../../../../constants';
 
 import { UNMastersListStyles } from './styles';
 
-import ArrowUpWideIcon from '../../../../../assets/icons/arrow-up-wide-short.svg';
-
 import type { Master } from '../../../../database/unMastersService';
 import { Colors } from '../../../../theme';
 import { getFontSize } from '../../../../utils';
@@ -124,20 +122,17 @@ const UNMastersList = React.memo(({ type }: { type: string }) => {
     const CountryItem = ({
       country,
       masters,
-      count,
-      flag
+      count
     }: {
       country: string;
       masters: Master[];
       count: number;
-      flag: string;
     }) => {
       if (masters.length === 0) return;
 
       return (
         <View style={UNMastersListStyles.countryAndYearItemWrapper}>
           <View style={UNMastersListStyles.countryAndYearHeader}>
-            <Image source={{ uri: API_HOST + flag }} style={{ width: 24, height: 16 }} />
             <Text style={{ color: Colors.DARK_BLUE, fontSize: getFontSize(18), fontWeight: '700' }}>
               {country} ({count})
             </Text>
@@ -184,12 +179,7 @@ const UNMastersList = React.memo(({ type }: { type: string }) => {
       type !== '-1' ? (
         <UserItem user={item as Master} />
       ) : (
-        <CountryItem
-          country={item.country}
-          masters={item.masters}
-          count={item.count}
-          flag={item.flag}
-        />
+        <CountryItem country={item.country} masters={item.masters} count={item.count} />
       )
     ) : (
       <YearItem year={item.year} masters={item.masters} count={item.count} />

+ 1 - 0
src/types/navigation.ts

@@ -24,6 +24,7 @@ export enum NAVIGATION_PAGES {
   TRAVELS_TAB = 'inAppTravels',
   SERIES = 'inAppSeries',
   SERIES_ITEM = 'inAppSeriesItem',
+  STATISTICS_LIST_DATA = 'statisticsListData',
   EARTH = 'inAppEarth',
   PHOTOS = 'inAppPhotos',
   MORE_PHOTOS = 'inAppMorePhotos',