Viktoriia преди 1 месец
родител
ревизия
24c10631fa

+ 7 - 2
src/components/ErrorModal/index.tsx

@@ -16,7 +16,8 @@ import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
 
 export const ErrorModal = () => {
-  const { error, hideError, navigateToLogin, navigateToAuth, resetErrorState } = useError();
+  const { error, hideError, navigateToLogin, navigateToAuth, premiumError, resetErrorState } =
+    useError();
   const navigation = useNavigation();
   const updateNotificationStatus = useFriendsNotificationsStore(
     (state) => state.updateNotificationStatus
@@ -61,7 +62,11 @@ export const ErrorModal = () => {
           </View>
           <View style={styles.modalContent}>
             <Text style={styles.modalTitle}>Oops!</Text>
-            <Text style={styles.modalText}>An error occurred: {error}</Text>
+            <Text style={styles.modalText}>
+              {premiumError
+                ? 'This feature is available to Premium users. Premium account settings can be managed on our website.'
+                : `An error occurred: ${error}`}
+            </Text>
             <View style={styles.buttonContainer}>
               <Button
                 variant={ButtonVariants.OPACITY}

+ 20 - 3
src/contexts/ErrorContext.tsx

@@ -8,13 +8,15 @@ const ErrorContext = createContext<{
   resetErrorState: () => void;
   navigateToLogin: boolean;
   navigateToAuth: boolean;
+  premiumError: boolean;
 }>({
   error: null,
   showError: (message: string, loginNeeded: boolean) => {},
   hideError: () => {},
   resetErrorState: () => {},
   navigateToLogin: false,
-  navigateToAuth: false
+  navigateToAuth: false,
+  premiumError: false
 });
 
 export const ErrorProvider = ({ children }: { children: React.ReactNode }) => {
@@ -22,13 +24,20 @@ export const ErrorProvider = ({ children }: { children: React.ReactNode }) => {
   const [error, setError] = useState<string | null>(null);
   const [navigateToLogin, setNavigateToLogin] = useState<boolean>(false);
   const [navigateToAuth, setNavigateToAuth] = useState<boolean>(false);
+  const [premiumError, setPremiumError] = useState<boolean>(false);
 
-  const showError = (message: string, loginNeeded: boolean, authNeeded: boolean = false) => {
+  const showError = (
+    message: string,
+    loginNeeded: boolean,
+    authNeeded: boolean = false,
+    premiumNeeded: boolean = false
+  ) => {
     if (!isErrorShown) {
       setErrorShown(true);
       setError(message);
       setNavigateToLogin(loginNeeded);
       setNavigateToAuth(authNeeded);
+      setPremiumError(premiumNeeded);
     }
   };
 
@@ -44,7 +53,15 @@ export const ErrorProvider = ({ children }: { children: React.ReactNode }) => {
 
   return (
     <ErrorContext.Provider
-      value={{ error, showError, hideError, resetErrorState, navigateToLogin, navigateToAuth }}
+      value={{
+        error,
+        showError,
+        hideError,
+        resetErrorState,
+        navigateToLogin,
+        navigateToAuth,
+        premiumError
+      }}
     >
       {children}
     </ErrorContext.Provider>

+ 1 - 0
src/modules/api/response-type.ts

@@ -13,4 +13,5 @@ export interface ResponseType {
   status?: StatusTypes;
   result_description?: string;
   login_needed?: number;
+  premium_needed?: number;
 }

+ 28 - 15
src/screens/InAppScreens/MapScreen/FilterModal/index.tsx

@@ -53,7 +53,8 @@ const FilterModal = ({
   isLogged = true,
   showNomads,
   setShowNomads,
-  usersOnMapCount
+  usersOnMapCount,
+  isConnected = true
 }: {
   isFilterVisible: string | null;
   setIsFilterVisible: (filterType: string | null) => void;
@@ -69,9 +70,13 @@ const FilterModal = ({
   showNomads?: boolean;
   setShowNomads?: (showNomads: boolean) => void;
   usersOnMapCount?: number | null;
+  isConnected?: boolean;
 }) => {
   const token = storage.get('token', StoreType.STRING) as string;
-  const { data: locationSettings } = usePostGetSettingsQuery(token, isLogged && !isPublicView);
+  const { data: locationSettings } = usePostGetSettingsQuery(
+    token,
+    isLogged && !isPublicView && isConnected
+  );
   const { mutateAsync: setSettings } = usePostSetSettingsMutation();
   const { mutateAsync: updateLocation } = usePostUpdateLocationMutation();
   const [selectedYear, setSelectedYear] = useState<{ label: string; value: number } | null>(null);
@@ -81,8 +86,12 @@ const FilterModal = ({
     { label: 'visited by', value: 0 },
     { label: 'visited in', value: 1 }
   ];
-  const { data } = usePostGetMapYearsQuery(token as string, userId, isLogged ? true : false);
-  const { data: seriesList } = useGetListQuery(true);
+  const { data } = usePostGetMapYearsQuery(
+    token as string,
+    userId,
+    isLogged && isConnected ? true : false
+  );
+  const { data: seriesList } = useGetListQuery(isConnected ? true : false);
   const [series, setSeries] = useState<{ label: string; value: number }[]>([]);
   const [selectedSeries, setSelectedSeries] = useState<number[]>([]);
   const [seriesVisible, setSeriesVisible] = useState(true);
@@ -200,17 +209,21 @@ const FilterModal = ({
 
   useEffect(() => {
     if (isFilterVisible && isFilterVisible === 'regions') {
-      saveFilterSettings();
-      handleApplyFilter();
-      setType(
-        tilesType.value === 2
-          ? 'dare'
-          : tilesType.value === 1
-            ? 'countries'
-            : tilesType.value === -1
-              ? 'blank'
-              : 'regions'
-      );
+      try {
+        saveFilterSettings();
+        handleApplyFilter();
+        setType(
+          tilesType.value === 2
+            ? 'dare'
+            : tilesType.value === 1
+              ? 'countries'
+              : tilesType.value === -1
+                ? 'blank'
+                : 'regions'
+        );
+      } catch (error) {
+        console.error('Error applying filter:', error);
+      }
     }
   }, [selectedYear, selectedVisible, tilesType]);
 

+ 20 - 7
src/screens/InAppScreens/MapScreen/index.tsx

@@ -331,7 +331,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
       regionsFilter.visitedLabel,
       regionsFilter.year,
       +userId,
-      type === 'regions' && !!userId
+      type === 'regions' && !!userId && isConnected
     );
   const { data: visitedCountryIds, refetch: refetchVisitedCountries } =
     usePostGetVisitedCountriesIdsQuery(
@@ -1810,8 +1810,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
             >
               <MapButton
                 onPress={() => {
-                  setIsFilterVisible('regions');
-                  closeCallout();
+                  try {
+                    setIsFilterVisible('regions');
+                    closeCallout();
+                  } catch (error) {
+                    console.error('Error opening filter:', error);
+                  }
                 }}
                 icon={TravelsIcon}
                 text="Travels"
@@ -1819,8 +1823,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
               />
               <MapButton
                 onPress={() => {
-                  setIsFilterVisible('series');
-                  closeCallout();
+                  try {
+                    setIsFilterVisible('series');
+                    closeCallout();
+                  } catch (error) {
+                    console.error('Error opening filter:', error);
+                  }
                 }}
                 icon={SeriesIcon}
                 text="Series"
@@ -1829,8 +1837,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
               {token ? (
                 <MapButton
                   onPress={() => {
-                    setIsFilterVisible('nomads');
-                    closeCallout();
+                    try {
+                      setIsFilterVisible('nomads');
+                      closeCallout();
+                    } catch (error) {
+                      console.error('Error opening filter:', error);
+                    }
                   }}
                   icon={NomadsIcon}
                   text="Nomads"
@@ -1903,6 +1915,7 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
         isPublicView={false}
         isLogged={token ? true : false}
         usersOnMapCount={token && usersOnMapCount?.count ? usersOnMapCount.count : null}
+        isConnected={isConnected}
       />
       <EditModal
         isVisible={isEditSlowModalVisible}

+ 4 - 26
src/screens/OfflineMapsScreen/OfflineMapManager.ts

@@ -40,8 +40,6 @@ const markPackAsCompleted = (packName) => {
 
     pendingDownloads = pendingDownloads.filter((name) => name !== packName);
     savePendingDownloads();
-
-    console.log(`Pack ${packName} marked as completed`);
   }
 };
 
@@ -59,10 +57,7 @@ const setupAppStateListener = () => {
         const isWifi = netInfo.type === 'wifi';
 
         if (isWifi || Platform.OS === 'android') {
-          console.log('App coming to foreground, resuming downloads');
           resumeAllPendingDownloads();
-        } else {
-          console.log('App coming to foreground, but not on WiFi, not resuming downloads');
         }
       } catch (error) {
         console.error('Error checking network when app came to foreground:', error);
@@ -100,7 +95,6 @@ const cleanupCompletedMaps = async () => {
           (status.percentage === 100 ||
             status.completedResourceCount === status.requiredResourceCount)
         ) {
-          console.log(`Found completed pack ${map.id} that was still marked as downloading`);
           markPackAsCompleted(map.id);
         }
       }
@@ -119,7 +113,6 @@ const subscribeToPackProgress = (packName, progressCallback, errorCallback) => {
     packName,
     (pack, status) => {
       if (status.completedResourceCount === status.requiredResourceCount) {
-        console.log(`Pack ${packName} download completed`);
         markPackAsCompleted(packName);
       }
 
@@ -231,9 +224,9 @@ const deletePack = async (name) => {
 
 const invalidatePack = async (name) => {
   try {
-    await MapLibreGL.OfflineManager.invalidatePack(name);
+    await MapLibreGL.OfflineManager.deletePack(name);
 
-    updateMapMetadata(name, { status: 'invalid' });
+    updateMapMetadata(name, { status: 'invalid', progress: 100 });
   } catch (error) {
     console.error(`Error invalidating pack ${name}:`, error);
     throw error;
@@ -250,14 +243,11 @@ const resumePackDownload = async (name: string, progressCallback?: any, errorCal
 
     if (pack) {
       const status = await pack.status();
-      console.log(`Pack ${name} status:`, status);
-
       if (
         status &&
         (status.percentage === 100 ||
           status.completedResourceCount === status.requiredResourceCount)
       ) {
-        console.log(`Pack ${name} is already complete`);
         markPackAsCompleted(name);
         return;
       }
@@ -270,13 +260,9 @@ const resumePackDownload = async (name: string, progressCallback?: any, errorCal
         status: 'downloading',
         isPaused: false
       });
-
-      console.log(`Resumed download for pack ${name}`);
       return;
     }
 
-    console.log(`Pack ${name} not found, recreating...`);
-
     const mapsString = storage.getString(OFFLINE_MAPS_KEY);
     if (!mapsString) {
       throw new Error('No maps data in storage');
@@ -316,8 +302,6 @@ const resumePackDownload = async (name: string, progressCallback?: any, errorCal
       pendingDownloads.push(name);
       savePendingDownloads();
     }
-
-    console.log(`Created new pack for ${name}`);
   } catch (error) {
     console.error(`Error resuming pack ${name}:`, error);
 
@@ -336,8 +320,6 @@ const resumeAllPendingDownloads = async () => {
 
   const downloadingMaps = maps.filter((map) => map.status === 'downloading' && !map.isPaused);
 
-  console.log(`Found ${downloadingMaps.length} maps to resume`);
-
   for (const map of downloadingMaps) {
     try {
       await resumePackDownload(map.id);
@@ -380,8 +362,6 @@ const cancelPackDownload = async (name) => {
       progress: 0,
       isPaused: false
     });
-
-    console.log(`Pack ${name} download canceled`);
   } catch (error) {
     console.error(`Error canceling pack ${name}:`, error);
     throw error;
@@ -410,14 +390,14 @@ const updatePack = async (name, progressCallback, errorCallback) => {
     try {
       await deletePack(name);
     } catch (error) {
-      console.log(`Error deleting pack ${name} before update:`, error);
+      console.error(`Error deleting pack ${name} before update:`, error);
     }
 
     const options = {
       name,
       styleURL: mapData.styleURL || `${VECTOR_MAP_HOST}/nomadmania-maps.json`,
       minZoom: mapData.minZoom || 10,
-      maxZoom: mapData.maxZoom || 20,
+      maxZoom: mapData.maxZoom || 14,
       bounds: [
         [bounds.west, bounds.south],
         [bounds.east, bounds.north]
@@ -436,8 +416,6 @@ const updatePack = async (name, progressCallback, errorCallback) => {
     });
 
     await createPack(options, progressCallback, errorCallback);
-
-    console.log(`Pack ${name} update started`);
   } catch (error) {
     console.error(`Error updating pack ${name}:`, error);
 

+ 78 - 51
src/screens/OfflineMapsScreen/SelectRegionsScreen/index.tsx

@@ -1,7 +1,7 @@
 import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { View, StyleSheet, TouchableOpacity, Text, ScrollView, Alert } from 'react-native';
+import { View, StyleSheet, TouchableOpacity, Text, ScrollView } from 'react-native';
 import { SafeAreaView } from 'react-native-safe-area-context';
-import { Modal, FlatList as List, Header } from 'src/components';
+import { Modal, FlatList as List, Header, WarningModal } from 'src/components';
 import * as turf from '@turf/turf';
 import * as MapLibreRN from '@maplibre/maplibre-react-native';
 import NetInfo from '@react-native-community/netinfo';
@@ -70,6 +70,16 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
   const cameraRef = useRef<MapLibreRN.CameraRef>(null);
 
   const [filterSelectedRegions, setFilterSelectedRegions] = useState<any[]>(generateFilter([]));
+  const [modalState, setModalState] = useState({
+    visible: false,
+    type: 'confirm',
+    action: () => {},
+    message: '',
+    title: ''
+  });
+
+  const pendingRegionRequests = new Set();
+  const [isLoading, setIsLoading] = useState(false);
 
   useEffect(() => {
     if (data && data.regions) {
@@ -77,6 +87,10 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
     }
   }, [data]);
 
+  useEffect(() => {
+    setIsLoading(pendingRegionRequests.size > 0);
+  }, [loadingSize]);
+
   useEffect(() => {
     const ids = selectedRegions.map((region) => region.id);
     setFilterSelectedRegions(generateFilter(ids));
@@ -96,6 +110,38 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
     checkConnectionType();
   }, []);
 
+  const closeModal = () => {
+    setModalState({
+      visible: false,
+      type: 'confirm',
+      action: () => {},
+      message: '',
+      title: ''
+    });
+  };
+
+  const fetchRegionData = async (regionId: number) => {
+    if (pendingRegionRequests.has(regionId)) {
+      return null;
+    }
+
+    pendingRegionRequests.add(regionId);
+    setLoadingSize(true);
+
+    try {
+      const response = await getMapDataForRegion({ token, region_id: regionId });
+      return response;
+    } catch (error) {
+      return null;
+    } finally {
+      pendingRegionRequests.delete(regionId);
+
+      if (pendingRegionRequests.size === 0) {
+        setLoadingSize(false);
+      }
+    }
+  };
+
   const addRegionFromSearch = async (searchRegion: any) => {
     setLoadingSize(true);
     const regionIndex = selectedRegions.findIndex((region) => region.id === searchRegion.id);
@@ -108,24 +154,12 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
       };
       setSelectedRegions([...selectedRegions, newRegion] as any);
 
-      await getMapDataForRegion(
-        { token, region_id: searchRegion.id },
-        {
-          onSuccess: (res) => {
-            console.log('Map data for region:', res);
-            if (res.size) {
-              setRegionsToSave((prevRegions) => [...prevRegions, { ...res, id: searchRegion.id }]);
-              setEstimatedSize((prevSize) => prevSize + res.size);
-            }
+      const regionData = await fetchRegionData(searchRegion.id);
 
-            setLoadingSize(false);
-          },
-          onError: (error) => {
-            console.error('Error fetching map data for region:', error);
-            setLoadingSize(false);
-          }
-        }
-      );
+      if (regionData && regionData.size) {
+        setRegionsToSave((prevRegions) => [...prevRegions, { ...regionData, id: searchRegion.id }]);
+        setEstimatedSize((prevSize) => prevSize + regionData.size);
+      }
 
       setRegionPopupVisible(true);
 
@@ -147,16 +181,15 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
   };
 
   const handleSavePress = () => {
-    console.log('Selected regions to save:', regionsToSave);
     if (!isConnectedToWifi) {
-      Alert.alert(
-        'Mobile Data Connection',
-        'You are not connected to WiFi. Downloading map tiles may use a significant amount of data. Do you want to continue?',
-        [
-          { text: 'Cancel', style: 'cancel' },
-          { text: 'Download', onPress: () => startDownload() }
-        ]
-      );
+      setModalState({
+        visible: true,
+        type: 'success',
+        action: () => startDownload(),
+        message:
+          "You're currently on a mobile connection. Downloading offline maps may use a large amount of data and could incur charges from your mobile provider. We recommend using Wi-Fi whenever possible.",
+        title: 'Mobile Data Connection'
+      });
     } else {
       startDownload();
     }
@@ -269,7 +302,7 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
 
   const handleMapPress = useCallback(
     async (event: any) => {
-      if (!mapRef.current) return;
+      if (!mapRef.current || isLoading) return;
       setLoadingSize(true);
 
       try {
@@ -295,39 +328,24 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
               const newSelectedRegions = [...selectedRegions];
               newSelectedRegions.splice(regionIndex, 1);
               setSelectedRegions(newSelectedRegions);
-              console.log('regionsToSave:', regionsToSave);
 
               setEstimatedSize((prevSize) => {
-                console.log('Region to remove:', regionToRemove, id);
-                console.log('Region to remove2:', regionsToSave);
-
                 return regionToRemove ? prevSize - regionToRemove.size : prevSize;
               });
+
               setRegionsToSave(regionsToSave.filter((region) => region.id !== id));
               setRegionPopupVisible(false);
               setLoadingSize(false);
               return;
             } else {
               setSelectedRegions([...selectedRegions, selectedRegion.properties] as any);
+              const regionData = await fetchRegionData(id);
 
-              await getMapDataForRegion(
-                { token, region_id: id },
-                {
-                  onSuccess: (res) => {
-                    console.log('Map data for region:', res);
-                    if (res.size) {
-                      setRegionsToSave((prevRegions) => [...prevRegions, { ...res, id }]);
-                      setEstimatedSize((prevSize) => prevSize + res.size);
-
-                      setLoadingSize(false);
-                    }
-                  },
-                  onError: (error) => {
-                    console.error('Error fetching map data for region:', error);
-                    setLoadingSize(false);
-                  }
-                }
-              );
+              if (regionData && regionData.size) {
+                setRegionsToSave((prevRegions) => [...prevRegions, { ...regionData, id }]);
+                setEstimatedSize((prevSize) => prevSize + regionData.size);
+                setLoadingSize(false);
+              }
             }
 
             handleSetRegionData(id);
@@ -467,6 +485,15 @@ export const SelectRegionScreen = ({ navigation }: { navigation: any }) => {
           }}
         />
       </Modal>
+
+      <WarningModal
+        type={modalState.type}
+        isVisible={modalState.visible}
+        message={modalState.message}
+        title={modalState.title}
+        action={modalState.action}
+        onClose={() => closeModal()}
+      />
     </SafeAreaView>
   );
 };

+ 137 - 62
src/screens/OfflineMapsScreen/index.tsx

@@ -15,26 +15,50 @@ import { offlineMapManager } from './OfflineMapManager';
 import { NAVIGATION_PAGES } from 'src/types';
 import { formatBytes } from './formatters';
 import { useSubscription } from './useSubscription';
-import { Header, Input, Loading, PageWrapper } from 'src/components';
+import { Header, Input, Loading, PageWrapper, WarningModal } from 'src/components';
 import { Colors } from 'src/theme';
 import { getFontSize } from 'src/utils';
 import * as Progress from 'react-native-progress';
 import { storage, StoreType } from 'src/storage';
+import EditIcon from 'assets/icons/travels-screens/pen-to-square.svg';
+import { usePostGetLastMapUpdateDateQuery } from '@api/maps';
 
 const OFFLINE_MAPS_KEY = 'offline_maps';
 
 export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
+  const token = storage.get('token', StoreType.STRING) as string;
   const contentWidth = Dimensions.get('window').width * 0.9;
+  const { data: lastMapUpdate } = usePostGetLastMapUpdateDateQuery(token, true);
   const [maps, setMaps] = useState([]);
   const [editingId, setEditingId] = useState(null);
   const [editingName, setEditingName] = useState('');
   const [loading, setLoading] = useState(true);
   const { isPremium } = useSubscription();
+  const [lastUpdate, setLastUpdate] = useState<string>(new Date().toISOString().slice(0, 10));
+  const [modalState, setModalState] = useState({
+    visible: false,
+    type: 'confirm',
+    action: () => {},
+    message: '',
+    title: ''
+  });
 
   useEffect(() => {
     offlineMapManager.init();
   }, []);
 
+  useEffect(() => {
+    if (lastMapUpdate) {
+      setLastUpdate(lastMapUpdate.last_update);
+    }
+  }, [lastMapUpdate]);
+
+  useEffect(() => {
+    if (lastUpdate) {
+      loadMaps();
+    }
+  }, [lastUpdate]);
+
   useEffect(() => {
     const intervalId = setInterval(() => {
       const mapsString = storage.get(OFFLINE_MAPS_KEY, StoreType.STRING) as string;
@@ -73,6 +97,14 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
           };
         }
 
+        const mapUpdateDate = new Date(map.updatedAt).toISOString().slice(0, 10);
+        if (mapUpdateDate < lastUpdate) {
+          return {
+            ...map,
+            status: 'invalid'
+          };
+        }
+
         return {
           ...map,
           status: isPremium && availablePackIds.includes(map.id) ? 'valid' : 'invalid'
@@ -87,7 +119,7 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
     } finally {
       setLoading(false);
     }
-  }, [isPremium]);
+  }, [isPremium, lastUpdate]);
 
   useFocusEffect(
     useCallback(() => {
@@ -103,6 +135,21 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
     }
   }, [isPremium]);
 
+  const hasDownloadingMaps = () => {
+    if (!maps) return false;
+    return maps.some((map) => map?.status === 'downloading' && map?.progress < 100);
+  };
+
+  const closeModal = () => {
+    setModalState({
+      visible: false,
+      type: 'confirm',
+      action: () => {},
+      message: '',
+      title: ''
+    });
+  };
+
   const restoreOfflineMaps = async () => {
     try {
       const savedMapsString = storage.get(OFFLINE_MAPS_KEY, StoreType.STRING) as string;
@@ -112,18 +159,16 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
 
       setLoading(true);
 
-      for (const map of savedMaps.filter((m: any) => m.status === 'invalid')) {
+      for (const map of savedMaps.filter((m: any) => m.status === 'invalid' && m.progress < 100)) {
         try {
           const pack = await offlineMapManager.getPack(map.id);
           if (!pack) {
-            console.log(`Pack ${map.id} not found, will be downloaded`);
-
             if (map.bounds) {
               await offlineMapManager.resumePackDownload(map.id);
             }
           }
         } catch (error) {
-          console.log(`Error checking pack ${map.id}:`, error);
+          console.error(`Error checking pack ${map.id}:`, error);
         }
       }
 
@@ -147,54 +192,50 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
   };
 
   const handleDelete = (id: string, name: string) => {
-    Alert.alert('Delete Offline Map', `Are you sure you want to delete "${name}"?`, [
-      { text: 'Cancel', style: 'cancel' },
-      {
-        text: 'Delete',
-        style: 'destructive',
-        onPress: async () => {
-          try {
-            const mapsString = storage.get(OFFLINE_MAPS_KEY, StoreType.STRING) as string;
-            const maps = mapsString ? JSON.parse(mapsString) : [];
-            const map = maps.find((m: any) => m.id === id);
-
-            if (map && map.status === 'downloading') {
-              await offlineMapManager.cancelPackDownload(id);
-            }
+    setModalState({
+      visible: true,
+      type: 'delete',
+      action: async () => {
+        try {
+          const mapsString = storage.get(OFFLINE_MAPS_KEY, StoreType.STRING) as string;
+          const maps = mapsString ? JSON.parse(mapsString) : [];
+          const map = maps.find((m: any) => m.id === id);
 
-            await offlineMapManager.deletePack(id);
+          if (map && map.status === 'downloading') {
+            await offlineMapManager.cancelPackDownload(id);
+          }
 
-            const updatedMaps = maps.filter((map) => map.id !== id);
-            storage.set(OFFLINE_MAPS_KEY, JSON.stringify(updatedMaps));
+          await offlineMapManager.deletePack(id);
 
-            setMaps(updatedMaps);
-          } catch (error) {
-            console.error('Error deleting offline map:', error);
-            Alert.alert('Error', 'Failed to delete offline map');
-          }
+          const updatedMaps = maps.filter((map: any) => map.id !== id);
+          storage.set(OFFLINE_MAPS_KEY, JSON.stringify(updatedMaps));
+
+          setMaps(updatedMaps);
+        } catch (error) {
+          console.error('Error deleting offline map:', error);
+          Alert.alert('Error', 'Failed to delete offline map');
         }
-      }
-    ]);
+      },
+      message: `Are you sure you want to delete "${name}"?`,
+      title: 'Delete Offline Map'
+    });
   };
 
-  // TO DO
-  // const handleUpdate = async (map) => {
-  //   try {
-  //     await offlineMapManager.updatePack(
-  //       map.id,
-  //       (progress) => {
-  //         console.log(`Update progress for ${map.id}:`, progress.percentage);
-  //       },
-  //       (error) => {
-  //         console.error(`Error updating map ${map.id}:`, error);
-  //       }
-  //     );
-
-  //     loadMaps();
-  //   } catch (error) {
-  //     console.error('Error updating offline map:', error);
-  //   }
-  // };
+  const handleUpdate = async (map: any) => {
+    try {
+      await offlineMapManager.updatePack(
+        map.id,
+        (progress: any) => {},
+        (error: any) => {
+          console.error(`Error updating map ${map.id}:`, error);
+        }
+      );
+
+      loadMaps();
+    } catch (error) {
+      console.error('Error updating offline map:', error);
+    }
+  };
 
   const handleEditName = (id, currentName) => {
     setEditingId(id);
@@ -207,7 +248,7 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
       return;
     }
 
-    const updatedMaps = maps.map((map) =>
+    const updatedMaps = maps.map((map: any) =>
       map.id === editingId ? { ...map, name: editingName } : map
     );
 
@@ -245,17 +286,17 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
       ) : (
         <View style={styles.mapInfoContainer}>
           <Text style={styles.mapName}>{item.name}</Text>
-          <TouchableOpacity
+          {/* <TouchableOpacity
             onPress={() => handleEditName(item.id, item.name)}
             style={styles.iconButton}
           >
-            <Ionicons name="pencil" size={16} color={Colors.DARK_BLUE} />
-          </TouchableOpacity>
+            <EditIcon height={16} width={16} color={Colors.DARK_BLUE} />
+          </TouchableOpacity> */}
         </View>
       )}
 
       <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
-        <View>
+        <View style={{ gap: 2 }}>
           <Text style={styles.mapDetails}>Size: {formatBytes(item.size)}</Text>
           <Text style={styles.mapDetails}>
             Updated: {new Date(item.updatedAt).toLocaleDateString()}
@@ -301,8 +342,7 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
           {isPremium && (
             <View style={styles.actionsContainer}>
               <TouchableOpacity
-                // TO DO
-                // onPress={() => handleUpdate(item)}
+                onPress={() => handleUpdate(item)}
                 style={[styles.actionButton, styles.updateButton]}
                 disabled={!isPremium}
               >
@@ -342,17 +382,29 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
             if (isPremium) {
               navigation.navigate(NAVIGATION_PAGES.OFFLINE_SELECT_REGIONS);
             } else {
-              Alert.alert(
-                'Premium Feature',
-                'Offline maps are available only with premium subscription.',
-                [{ text: 'OK' }]
-              );
+              setModalState({
+                visible: true,
+                type: 'success',
+                action: () => {},
+                message:
+                  'This feature is available to Premium users. Premium account settings can be managed on our website.',
+                title: 'Premium Feature'
+              });
             }
           }}
         >
           <Text style={styles.buttonText}>Select regions</Text>
         </TouchableOpacity>
 
+        {hasDownloadingMaps() && (
+          <View style={styles.downloadingNoticeContainer}>
+            <Text style={styles.downloadingNoticeText}>
+              Offline map downloading requires the app to stay open and active. Downloads will pause
+              if you leave the app or lock your screen.
+            </Text>
+          </View>
+        )}
+
         {loading ? (
           <View style={styles.loadingContainer}>
             <Loading />
@@ -372,11 +424,33 @@ export default function OfflineMapsScreen({ navigation }: { navigation: any }) {
           />
         )}
       </ScrollView>
+
+      <WarningModal
+        type={modalState.type}
+        isVisible={modalState.visible}
+        message={modalState.message}
+        title={modalState.title}
+        action={modalState.action}
+        onClose={() => closeModal()}
+      />
     </PageWrapper>
   );
 }
 
 const styles = StyleSheet.create({
+  downloadingNoticeContainer: {
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 8,
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    alignItems: 'center',
+    gap: 8
+  },
+  downloadingNoticeText: {
+    fontSize: getFontSize(12),
+    color: Colors.TEXT_GRAY,
+    flex: 1
+  },
   container: {
     flex: 1,
     backgroundColor: '#fff',
@@ -406,7 +480,7 @@ const styles = StyleSheet.create({
   },
   listContainer: {
     paddingBottom: 16,
-    paddingTop: 10
+    paddingTop: 8
   },
   mapItem: {
     backgroundColor: Colors.FILL_LIGHT,
@@ -447,7 +521,8 @@ const styles = StyleSheet.create({
     paddingHorizontal: 12,
     paddingVertical: 6,
     borderRadius: 4,
-    marginLeft: 8
+    minWidth: 68,
+    alignItems: 'center'
   },
   updateButton: {
     backgroundColor: Colors.ORANGE

+ 13 - 4
src/utils/request.ts

@@ -12,7 +12,12 @@ export const request = axios.create({
 export const setupInterceptors = ({
   showError
 }: {
-  showError: (message: string, loginNeeded: boolean, authNeeded: boolean) => void;
+  showError: (
+    message: string,
+    loginNeeded: boolean,
+    authNeeded: boolean,
+    premiumNeeded: boolean
+  ) => void;
 }) => {
   request.interceptors.request.use(
     (config) => {
@@ -38,12 +43,13 @@ export const setupInterceptors = ({
         const showErrorWithDelay = (
           message: string,
           requiresLogin: boolean,
-          requiresAuth: boolean
+          requiresAuth: boolean,
+          requiresPremium: boolean = false
         ) => {
           if (!isErrorShown) {
             setErrorShown(true);
             setTimeout(() => {
-              showError(message, requiresLogin, requiresAuth);
+              showError(message, requiresLogin, requiresAuth, requiresPremium);
             }, 1000);
           }
         };
@@ -57,6 +63,9 @@ export const setupInterceptors = ({
         ) {
           showErrorWithDelay(response.data.result_description, false, true);
           return response;
+        } else if (response.data?.premium_needed && response.data.premium_needed === 1) {
+          showErrorWithDelay(response.data.result_description, false, false, true);
+          return response;
         }
         showErrorWithDelay(response.data.result_description, false, false);
       }
@@ -80,7 +89,7 @@ export const setupInterceptors = ({
 
       if (!isErrorShown) {
         setErrorShown(true);
-        showError(`${error.message} (${shortUrl})`, false, false);
+        showError(`${error.message} (${shortUrl})`, false, false, false);
       }
 
       return Promise.reject(error);