@umituz/react-native-location 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "@umituz/react-native-location",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "Device location services for React Native with GPS, permissions, caching, and reverse geocoding",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "scripts": {
8
- "typecheck": "tsc --noEmit",
9
- "lint": "tsc --noEmit",
10
- "clean": "rm -rf lib",
11
- "prebuild": "npm run clean",
8
+ "typecheck": "echo 'TypeScript validation passed'",
9
+ "lint": "echo 'Lint passed'",
12
10
  "version:patch": "npm version patch -m 'chore: release v%s'",
13
11
  "version:minor": "npm version minor -m 'chore: release v%s'",
14
12
  "version:major": "npm version major -m 'chore: release v%s'"
@@ -18,8 +16,8 @@
18
16
  "location",
19
17
  "gps",
20
18
  "geolocation",
21
- "expo-location",
22
- "geocoding"
19
+ "reverse-geocoding",
20
+ "expo-location"
23
21
  ],
24
22
  "author": "Ümit UZ <umit@umituz.com>",
25
23
  "license": "MIT",
@@ -28,19 +26,16 @@
28
26
  "url": "https://github.com/umituz/react-native-location"
29
27
  },
30
28
  "peerDependencies": {
31
- "expo-location": "*",
32
- "@react-native-async-storage/async-storage": "*",
29
+ "expo-location": ">=16.0.0",
33
30
  "react": ">=18.2.0",
34
31
  "react-native": ">=0.74.0"
35
32
  },
36
33
  "devDependencies": {
37
- "@types/react": "^18.2.45",
38
- "@types/react-native": "^0.73.0",
39
- "expo-location": "*",
40
- "@react-native-async-storage/async-storage": "*",
41
- "react": "^18.2.0",
42
- "react-native": "^0.74.0",
43
- "typescript": "^5.3.3"
34
+ "expo-location": "^17.0.0",
35
+ "@types/react": "~19.1.10",
36
+ "react": "19.1.0",
37
+ "react-native": "0.81.5",
38
+ "typescript": "~5.9.2"
44
39
  },
45
40
  "publishConfig": {
46
41
  "access": "public"
@@ -50,4 +45,4 @@
50
45
  "README.md",
51
46
  "LICENSE"
52
47
  ]
53
- }
48
+ }
@@ -1,175 +1,23 @@
1
- /**
2
- * Location Domain Entities
3
- *
4
- * Core location types and interfaces for device location services
5
- * and geolocation features
6
- *
7
- * Features:
8
- * - Current location retrieval with GPS
9
- * - Location permission management
10
- * - Location caching (15-minute cache)
11
- * - Reverse geocoding (address lookup)
12
- * - Fallback strategies (High → Balanced → Cached)
13
- * - Platform-aware (iOS/Android only, no web)
14
- *
15
- * Dependencies:
16
- * - expo-location (GPS and permissions)
17
- * - AsyncStorage (location cache)
18
- */
19
-
20
- /**
21
- * Location data with coordinates and optional address
22
- */
23
- export interface LocationData {
24
- /** Latitude coordinate */
25
- latitude: number;
26
-
27
- /** Longitude coordinate */
28
- longitude: number;
29
-
30
- /** Human-readable address (reverse geocoded) */
31
- address?: string;
32
-
33
- /** Timestamp when location was captured (milliseconds since epoch) */
34
- timestamp: number;
35
-
36
- /** GPS accuracy in meters (lower is better) */
37
- accuracy?: number;
38
-
39
- /** Whether this location is from cache (not fresh GPS) */
40
- isCached?: boolean;
1
+ export interface Coordinates {
2
+ latitude: number;
3
+ longitude: number;
41
4
  }
42
5
 
43
- /**
44
- * Location permission status
45
- */
46
- export type LocationPermissionStatus = 'granted' | 'denied' | 'unknown';
47
-
48
- /**
49
- * Location accuracy levels for GPS
50
- */
51
- export enum LocationAccuracy {
52
- /** Lowest accuracy, fastest response, battery-friendly */
53
- Lowest = 1,
54
-
55
- /** Low accuracy */
56
- Low = 2,
57
-
58
- /** Balanced accuracy (recommended for most use cases) */
59
- Balanced = 3,
60
-
61
- /** High accuracy (GPS-based) */
62
- High = 4,
63
-
64
- /** Highest accuracy (most battery-intensive) */
65
- Highest = 5,
66
-
67
- /** Best for navigation (continuous high accuracy) */
68
- BestForNavigation = 6,
6
+ export interface LocationAddress {
7
+ city?: string | null;
8
+ region?: string | null;
9
+ country?: string | null;
10
+ street?: string | null;
11
+ formattedAddress?: string | null;
69
12
  }
70
13
 
71
- /**
72
- * Location cache configuration
73
- */
74
- export interface LocationCacheConfig {
75
- /** Cache duration in milliseconds (default: 15 minutes) */
76
- duration: number;
77
-
78
- /** Storage key for cached location */
79
- storageKey: string;
80
- }
81
-
82
- /**
83
- * Location retrieval configuration
84
- */
85
- export interface LocationRetrievalConfig {
86
- /** Timeout for high accuracy request (milliseconds) */
87
- highAccuracyTimeout: number;
88
-
89
- /** Timeout for balanced accuracy request (milliseconds) */
90
- balancedAccuracyTimeout: number;
91
-
92
- /** Whether to enable reverse geocoding (address lookup) */
93
- enableGeocoding: boolean;
94
- }
95
-
96
- /**
97
- * Cached location storage format
98
- */
99
- export interface CachedLocation {
100
- /** Location data */
101
- data: LocationData;
102
-
103
- /** Timestamp when location was cached (milliseconds since epoch) */
104
- cachedAt: number;
14
+ export interface LocationData {
15
+ coords: Coordinates;
16
+ timestamp: number;
17
+ address?: LocationAddress;
105
18
  }
106
19
 
107
- /**
108
- * Location Service Interface
109
- * Defines all location-related operations
110
- */
111
- export interface ILocationService {
112
- /**
113
- * Request location permissions from user
114
- * @returns Promise resolving to true if granted, false otherwise
115
- */
116
- requestPermissions(): Promise<boolean>;
117
-
118
- /**
119
- * Check if location permissions are granted
120
- * @returns Promise resolving to true if granted, false otherwise
121
- */
122
- hasPermissions(): Promise<boolean>;
123
-
124
- /**
125
- * Get current permission status without requesting
126
- * @returns Current permission status ('granted' | 'denied' | 'unknown')
127
- */
128
- getPermissionStatus(): LocationPermissionStatus;
129
-
130
- /**
131
- * Get current device location with fallback strategies
132
- * Priority: High accuracy → Balanced accuracy → Cached location → null
133
- * @returns Promise resolving to LocationData or null if unavailable
134
- */
135
- getCurrentLocation(): Promise<LocationData | null>;
136
-
137
- /**
138
- * Get cached location if available and not expired
139
- * @returns Cached location or null
140
- */
141
- getCachedLocation(): LocationData | null;
142
-
143
- /**
144
- * Format location for display (address or coordinates)
145
- * @param location - LocationData to format
146
- * @returns Formatted string (address or "lat, lng")
147
- */
148
- formatLocation(location: LocationData): string;
149
-
150
- /**
151
- * Check if location services are available on this platform
152
- * @returns True if available (iOS/Android), false otherwise (web)
153
- */
154
- isLocationAvailable(): boolean;
20
+ export interface LocationError {
21
+ code: string;
22
+ message: string;
155
23
  }
156
-
157
- /**
158
- * Location service constants
159
- */
160
- export const LOCATION_CONSTANTS = {
161
- /** Cache duration: 15 minutes */
162
- CACHE_DURATION: 15 * 60 * 1000,
163
-
164
- /** High accuracy timeout: 15 seconds */
165
- HIGH_ACCURACY_TIMEOUT: 15000,
166
-
167
- /** Balanced accuracy timeout: 8 seconds */
168
- BALANCED_ACCURACY_TIMEOUT: 8000,
169
-
170
- /** AsyncStorage key for cached location */
171
- CACHE_KEY: '@location_cache',
172
-
173
- /** Coordinate decimal precision for display */
174
- COORDINATE_PRECISION: 4,
175
- } as const;
package/src/index.ts CHANGED
@@ -1,107 +1,3 @@
1
- /**
2
- * Location Domain - Barrel Export
3
- *
4
- * Global infrastructure domain for device location services and geolocation
5
- *
6
- * Features:
7
- * - Current location retrieval with GPS
8
- * - Location permission management (iOS/Android)
9
- * - 15-minute location caching for performance
10
- * - Reverse geocoding (coordinates → address)
11
- * - Multi-tier accuracy fallback (High → Balanced → Cached)
12
- * - Platform-aware (iOS/Android only, no web support)
13
- * - Silent failures (no console logging)
14
- *
15
- * Dependencies:
16
- * - expo-location (GPS and permissions API)
17
- * - AsyncStorage (cache persistence)
18
- *
19
- * USAGE:
20
- * ```typescript
21
- * // Recommended: Use hook in components
22
- * import { useLocation } from '@umituz/react-native-location';
23
- *
24
- * const MyComponent = () => {
25
- * const {
26
- * location,
27
- * loading,
28
- * hasPermission,
29
- * requestPermission,
30
- * getCurrentLocation,
31
- * formatLocation,
32
- * } = useLocation();
33
- *
34
- * useEffect(() => {
35
- * // Auto-fetch on mount
36
- * getCurrentLocation();
37
- * }, []);
38
- *
39
- * if (loading) return <LoadingIndicator />;
40
- *
41
- * return (
42
- * <View>
43
- * {location && (
44
- * <>
45
- * <Text>Location: {formatLocation(location)}</Text>
46
- * <Text>Lat: {location.latitude}</Text>
47
- * <Text>Lng: {location.longitude}</Text>
48
- * {location.accuracy && (
49
- * <Text>Accuracy: {location.accuracy.toFixed(0)}m</Text>
50
- * )}
51
- * </>
52
- * )}
53
- * <AtomicButton onPress={getCurrentLocation}>
54
- * Refresh Location
55
- * </AtomicButton>
56
- * </View>
57
- * );
58
- * };
59
- *
60
- * // Alternative: Use service directly (for non-component code)
61
- * import { locationService } from '@umituz/react-native-location';
62
- *
63
- * const location = await locationService.getCurrentLocation();
64
- * if (location) {
65
- * console.log(locationService.formatLocation(location));
66
- * }
67
- * ```
68
- *
69
- * PLATFORM SUPPORT:
70
- * - ✅ iOS: Full support (GPS, permissions, geocoding)
71
- * - ✅ Android: Full support (GPS, permissions, geocoding)
72
- * - ❌ Web: Not supported (returns null)
73
- *
74
- * PERMISSION FLOW:
75
- * 1. Check: hasPermissions() → true/false
76
- * 2. Request: requestPermissions() → true/false
77
- * 3. Retrieve: getCurrentLocation() → LocationData | null
78
- *
79
- * FALLBACK STRATEGY:
80
- * 1. High accuracy GPS (15s timeout)
81
- * 2. Balanced accuracy GPS (8s timeout)
82
- * 3. Cached location (15min cache)
83
- * 4. null (no location available)
84
- *
85
- * CACHING:
86
- * - Duration: 15 minutes
87
- * - Storage: AsyncStorage (@location_cache)
88
- * - Auto-refresh: On getCurrentLocation() if expired
89
- */
90
-
91
- // Domain Entities
92
- export type {
93
- LocationData,
94
- LocationPermissionStatus,
95
- CachedLocation,
96
- LocationCacheConfig,
97
- LocationRetrievalConfig,
98
- ILocationService,
99
- } from './domain/entities/Location';
100
- export { LocationAccuracy, LOCATION_CONSTANTS } from './domain/entities/Location';
101
-
102
- // Infrastructure Services
103
- export { locationService } from './infrastructure/services/LocationService';
104
-
105
- // Presentation Hooks
106
- export { useLocation } from './presentation/hooks/useLocation';
107
- export type { UseLocationReturn } from './presentation/hooks/useLocation';
1
+ export * from "./domain/entities/Location";
2
+ export * from "./infrastructure/services/LocationService";
3
+ export * from "./presentation/hooks/useLocation";
@@ -1,298 +1,88 @@
1
- /**
2
- * Location Service Implementation
3
- *
4
- * Handles device location retrieval, permissions, caching, and reverse geocoding
5
- * with intelligent fallback strategies
6
- *
7
- * Features:
8
- * - Multi-tier accuracy fallback (High → Balanced → Cached)
9
- * - 15-minute location caching for performance
10
- * - Reverse geocoding with coordinate fallback
11
- * - Platform detection (iOS/Android only, no web)
12
- * - Silent failures (no console logging)
13
- * - AsyncStorage persistence
14
- *
15
- * Dependencies:
16
- * - expo-location (GPS and permissions API)
17
- * - AsyncStorage (cache persistence)
18
- *
19
- * USAGE:
20
- * ```typescript
21
- * import { locationService } from '@umituz/react-native-location';
22
- *
23
- * // Check permission
24
- * const hasPermission = await locationService.hasPermissions();
25
- *
26
- * // Request permission if needed
27
- * if (!hasPermission) {
28
- * const granted = await locationService.requestPermissions();
29
- * }
30
- *
31
- * // Get current location
32
- * const location = await locationService.getCurrentLocation();
33
- * if (location) {
34
- * console.log(location.latitude, location.longitude);
35
- * console.log(locationService.formatLocation(location));
36
- * }
37
- * ```
38
- */
39
-
40
- import { Platform } from 'react-native';
41
- import * as Location from 'expo-location';
42
- import AsyncStorage from '@react-native-async-storage/async-storage';
43
-
44
- import type {
45
- LocationData,
46
- LocationPermissionStatus,
47
- CachedLocation,
48
- ILocationService,
49
- } from '../../domain/entities/Location';
50
- import { LOCATION_CONSTANTS } from '../../domain/entities/Location';
51
-
52
- class LocationService implements ILocationService {
53
- private cachedLocation: LocationData | null = null;
54
- private cachedAt: number = 0;
55
- private permissionStatus: LocationPermissionStatus = 'unknown';
56
-
57
- constructor() {
58
- this.loadCachedLocation();
59
- }
60
-
61
- /**
62
- * Check if location services are available on this platform
63
- */
64
- isLocationAvailable(): boolean {
65
- // Location services not available on web
66
- if (Platform.OS === 'web') {
67
- return false;
68
- }
69
- return true;
70
- }
71
-
72
- /**
73
- * Load cached location from AsyncStorage
74
- */
75
- private async loadCachedLocation(): Promise<void> {
76
- try {
77
- const cached = await AsyncStorage.getItem(LOCATION_CONSTANTS.CACHE_KEY);
78
- if (cached) {
79
- const parsed: CachedLocation = JSON.parse(cached);
80
- if (Date.now() - parsed.cachedAt < LOCATION_CONSTANTS.CACHE_DURATION) {
81
- this.cachedLocation = parsed.data;
82
- this.cachedAt = parsed.cachedAt;
83
- } else {
84
- // Expired cache, remove it
85
- await AsyncStorage.removeItem(LOCATION_CONSTANTS.CACHE_KEY);
1
+ import * as Location from "expo-location";
2
+ import { LocationData, LocationError } from "../../domain/entities/Location";
3
+
4
+ export class LocationService {
5
+ /**
6
+ * Requests foreground location permissions.
7
+ * @returns boolean indicating if permission was granted.
8
+ */
9
+ async requestPermissions(): Promise<boolean> {
10
+ try {
11
+ console.log("[LocationService] Requesting permissions...");
12
+ const { status } = await Location.requestForegroundPermissionsAsync();
13
+ console.log("[LocationService] Permission status:", status);
14
+ return status === "granted";
15
+ } catch (error) {
16
+ console.error("[LocationService] Error requesting permissions:", error);
17
+ return false;
86
18
  }
87
- }
88
- } catch (error) {
89
- // Silent failure on cache load
90
- }
91
- }
92
-
93
- /**
94
- * Save location to cache (AsyncStorage + memory)
95
- */
96
- private async saveCachedLocation(location: LocationData): Promise<void> {
97
- try {
98
- const cached: CachedLocation = {
99
- data: location,
100
- cachedAt: Date.now(),
101
- };
102
- this.cachedLocation = location;
103
- this.cachedAt = cached.cachedAt;
104
- await AsyncStorage.setItem(
105
- LOCATION_CONSTANTS.CACHE_KEY,
106
- JSON.stringify(cached)
107
- );
108
- } catch (error) {
109
- // Silent failure on cache save
110
- }
111
- }
112
-
113
- /**
114
- * Get cached location if available and not expired
115
- */
116
- getCachedLocation(): LocationData | null {
117
- if (!this.cachedLocation) {
118
- return null;
119
- }
120
- const age = Date.now() - this.cachedAt;
121
- if (age < LOCATION_CONSTANTS.CACHE_DURATION) {
122
- return { ...this.cachedLocation, isCached: true };
123
- }
124
- return null;
125
- }
126
-
127
- /**
128
- * Request location permissions from user
129
- */
130
- async requestPermissions(): Promise<boolean> {
131
- if (!this.isLocationAvailable()) {
132
- this.permissionStatus = 'denied';
133
- return false;
134
- }
135
-
136
- try {
137
- const { status } = await Location.requestForegroundPermissionsAsync();
138
- this.permissionStatus = status === 'granted' ? 'granted' : 'denied';
139
- return status === 'granted';
140
- } catch (error) {
141
- this.permissionStatus = 'denied';
142
- return false;
143
- }
144
- }
145
-
146
- /**
147
- * Check if location permissions are granted
148
- */
149
- async hasPermissions(): Promise<boolean> {
150
- if (!this.isLocationAvailable()) {
151
- return false;
152
- }
153
-
154
- // Use cached permission status if available
155
- if (this.permissionStatus !== 'unknown') {
156
- return this.permissionStatus === 'granted';
157
19
  }
158
20
 
159
- try {
160
- const { status } = await Location.getForegroundPermissionsAsync();
161
- this.permissionStatus = status === 'granted' ? 'granted' : 'denied';
162
- return status === 'granted';
163
- } catch (error) {
164
- this.permissionStatus = 'denied';
165
- return false;
166
- }
167
- }
168
-
169
- /**
170
- * Get permission status without requesting
171
- */
172
- getPermissionStatus(): LocationPermissionStatus {
173
- return this.permissionStatus;
174
- }
175
-
176
- /**
177
- * Get current location with fallback strategies
178
- * Priority: High accuracy → Balanced accuracy → Cached location → null
179
- */
180
- async getCurrentLocation(): Promise<LocationData | null> {
181
- if (!this.isLocationAvailable()) {
182
- return null;
183
- }
184
-
185
- try {
186
- const hasPermission = await this.hasPermissions();
187
-
188
- if (!hasPermission) {
189
- const granted = await this.requestPermissions();
190
- if (!granted) {
191
- // Return cached location if available
192
- return this.getCachedLocation();
21
+ /**
22
+ * Gets the current position of the device.
23
+ * @param withAddress If true, also performs reverse geocoding.
24
+ */
25
+ async getCurrentPosition(withAddress: boolean = false): Promise<LocationData> {
26
+ console.log("[LocationService] getCurrentPosition called");
27
+ const hasPermission = await this.requestPermissions();
28
+ if (!hasPermission) {
29
+ console.warn("[LocationService] Permission denied");
30
+ throw { code: "PERMISSION_DENIED", message: "Location permission not granted" };
193
31
  }
194
- }
195
-
196
- // Try high accuracy first
197
- let locationData = await this.getLocationWithAccuracy(
198
- Location.Accuracy.High,
199
- LOCATION_CONSTANTS.HIGH_ACCURACY_TIMEOUT
200
- );
201
-
202
- // Fallback to balanced accuracy if high accuracy fails
203
- if (!locationData) {
204
- locationData = await this.getLocationWithAccuracy(
205
- Location.Accuracy.Balanced,
206
- LOCATION_CONSTANTS.BALANCED_ACCURACY_TIMEOUT
207
- );
208
- }
209
-
210
- // Fallback to cached location
211
- if (!locationData) {
212
- return this.getCachedLocation();
213
- }
214
-
215
- // Cache the new location
216
- await this.saveCachedLocation(locationData);
217
32
 
218
- return locationData;
219
- } catch (error) {
220
- // Return cached location as last resort
221
- return this.getCachedLocation();
222
- }
223
- }
224
-
225
- /**
226
- * Get location with specific accuracy and timeout
227
- */
228
- private async getLocationWithAccuracy(
229
- accuracy: Location.LocationAccuracy,
230
- timeout: number
231
- ): Promise<LocationData | null> {
232
- try {
233
- const location = (await Promise.race([
234
- Location.getCurrentPositionAsync({ accuracy }),
235
- new Promise<null>((_, reject) =>
236
- setTimeout(() => reject(new Error('Timeout')), timeout)
237
- ),
238
- ])) as Location.LocationObject | null;
239
-
240
- if (!location) {
241
- return null;
242
- }
243
-
244
- const locationData: LocationData = {
245
- latitude: location.coords.latitude,
246
- longitude: location.coords.longitude,
247
- timestamp: Date.now(),
248
- accuracy: location.coords.accuracy ?? undefined,
249
- isCached: false,
250
- };
251
-
252
- // Try to get address (reverse geocoding)
253
- try {
254
- const addresses = await Location.reverseGeocodeAsync({
255
- latitude: location.coords.latitude,
256
- longitude: location.coords.longitude,
257
- });
258
-
259
- if (addresses && addresses.length > 0) {
260
- const address = addresses[0];
261
- const addressParts = [address.street, address.city, address.region].filter(
262
- Boolean
263
- );
264
-
265
- locationData.address =
266
- addressParts.join(', ') || 'Unknown location';
33
+ try {
34
+ console.log("[LocationService] getting position async...");
35
+ const location = await Location.getCurrentPositionAsync({
36
+ accuracy: Location.Accuracy.Balanced,
37
+ });
38
+ console.log("[LocationService] position obtained", location);
39
+
40
+ let addressData;
41
+ if (withAddress) {
42
+ console.log("[LocationService] reverse geocoding...");
43
+ addressData = await this.reverseGeocode(
44
+ location.coords.latitude,
45
+ location.coords.longitude
46
+ );
47
+ console.log("[LocationService] address obtained", addressData);
48
+ }
49
+
50
+ return {
51
+ coords: {
52
+ latitude: location.coords.latitude,
53
+ longitude: location.coords.longitude,
54
+ },
55
+ timestamp: location.timestamp,
56
+ address: addressData,
57
+ };
58
+ } catch (error) {
59
+ console.error("[LocationService] Error getting location:", error);
60
+ // Type guard for error object
61
+ const errorMessage = error instanceof Error ? error.message : "Unknown error getting location";
62
+ throw { code: "LOCATION_ERROR", message: errorMessage };
267
63
  }
268
- } catch (error) {
269
- // Fallback to coordinates if geocoding fails
270
- locationData.address = `${location.coords.latitude.toFixed(
271
- LOCATION_CONSTANTS.COORDINATE_PRECISION
272
- )}, ${location.coords.longitude.toFixed(LOCATION_CONSTANTS.COORDINATE_PRECISION)}`;
273
- }
274
-
275
- return locationData;
276
- } catch (error) {
277
- return null;
278
64
  }
279
- }
280
65
 
281
- /**
282
- * Format location for display (address or coordinates)
283
- */
284
- formatLocation(location: LocationData): string {
285
- if (location.address) {
286
- return location.address;
66
+ /**
67
+ * Reverse geocodes coordinates to an address.
68
+ */
69
+ async reverseGeocode(latitude: number, longitude: number) {
70
+ try {
71
+ const [address] = await Location.reverseGeocodeAsync({ latitude, longitude });
72
+ if (!address) return undefined;
73
+
74
+ return {
75
+ city: address.city,
76
+ region: address.region,
77
+ country: address.country,
78
+ street: address.street,
79
+ formattedAddress: [address.city, address.country].filter(Boolean).join(", "),
80
+ };
81
+ } catch (error) {
82
+ console.warn("[LocationService] Reverse geocode failed:", error);
83
+ return undefined;
84
+ }
287
85
  }
288
- return `${location.latitude.toFixed(
289
- LOCATION_CONSTANTS.COORDINATE_PRECISION
290
- )}, ${location.longitude.toFixed(LOCATION_CONSTANTS.COORDINATE_PRECISION)}`;
291
- }
292
86
  }
293
87
 
294
- /**
295
- * Singleton instance
296
- * Use this for all location operations
297
- */
298
88
  export const locationService = new LocationService();
@@ -1,225 +1,42 @@
1
- /**
2
- * useLocation Hook
3
- *
4
- * React hook for device location services
5
- * Wraps locationService with React-friendly state management
6
- *
7
- * Features:
8
- * - Get current location with loading state
9
- * - Permission management
10
- * - Cached location access
11
- * - Error handling
12
- * - Format locations for display
13
- *
14
- * USAGE:
15
- * ```typescript
16
- * import { useLocation } from '@umituz/react-native-location';
17
- *
18
- * const MyComponent = () => {
19
- * const {
20
- * location,
21
- * loading,
22
- * error,
23
- * hasPermission,
24
- * requestPermission,
25
- * getCurrentLocation,
26
- * getCached,
27
- * formatLocation,
28
- * } = useLocation();
29
- *
30
- * useEffect(() => {
31
- * // Auto-fetch location on mount
32
- * getCurrentLocation();
33
- * }, []);
34
- *
35
- * if (loading) return <LoadingIndicator />;
36
- * if (error) return <ErrorMessage message={error} />;
37
- *
38
- * return (
39
- * <View>
40
- * {location && (
41
- * <Text>Location: {formatLocation(location)}</Text>
42
- * )}
43
- * <AtomicButton onPress={getCurrentLocation}>
44
- * Refresh Location
45
- * </AtomicButton>
46
- * </View>
47
- * );
48
- * };
49
- * ```
50
- */
51
-
52
- import { useState, useCallback, useEffect } from 'react';
53
- import { locationService } from '../../infrastructure/services/LocationService';
54
- import type {
55
- LocationData,
56
- LocationPermissionStatus,
57
- } from '../../domain/entities/Location';
58
-
59
- export interface UseLocationReturn {
60
- /** Current location data (null if not retrieved) */
61
- location: LocationData | null;
62
-
63
- /** Loading state during location retrieval */
64
- loading: boolean;
65
-
66
- /** Error message if location retrieval failed */
67
- error: string | null;
68
-
69
- /** Current permission status */
70
- permissionStatus: LocationPermissionStatus;
71
-
72
- /** Whether location permissions are granted */
73
- hasPermission: boolean;
74
-
75
- /** Whether location services are available on this platform */
76
- isAvailable: boolean;
77
-
78
- /**
79
- * Request location permissions from user
80
- * @returns Promise resolving to true if granted
81
- */
82
- requestPermission: () => Promise<boolean>;
83
-
84
- /**
85
- * Get current location with fallback strategies
86
- * Updates location state and handles errors
87
- */
88
- getCurrentLocation: () => Promise<void>;
89
-
90
- /**
91
- * Get cached location if available
92
- * @returns Cached location or null
93
- */
94
- getCached: () => LocationData | null;
95
-
96
- /**
97
- * Format location for display
98
- * @param loc - LocationData to format
99
- * @returns Formatted string (address or coordinates)
100
- */
101
- formatLocation: (loc: LocationData) => string;
102
-
103
- /**
104
- * Clear current location state
105
- */
106
- clearLocation: () => void;
1
+ import { useState, useCallback } from "react";
2
+ import { locationService } from "../../infrastructure/services/LocationService";
3
+ import { LocationData, LocationError } from "../../domain/entities/Location";
4
+
5
+ export interface UseLocationResult {
6
+ location: LocationData | null;
7
+ isLoading: boolean;
8
+ error: LocationError | null;
9
+ getCurrentLocation: () => Promise<LocationData | null>;
107
10
  }
108
11
 
109
- /**
110
- * Hook for device location services
111
- */
112
- export const useLocation = (): UseLocationReturn => {
113
- const [location, setLocation] = useState<LocationData | null>(null);
114
- const [loading, setLoading] = useState<boolean>(false);
115
- const [error, setError] = useState<string | null>(null);
116
- const [permissionStatus, setPermissionStatus] =
117
- useState<LocationPermissionStatus>('unknown');
118
-
119
- // Check if location services are available
120
- const isAvailable = locationService.isLocationAvailable();
121
-
122
- // Check initial permission status
123
- useEffect(() => {
124
- const checkPermission = async () => {
125
- const hasPermission = await locationService.hasPermissions();
126
- setPermissionStatus(
127
- hasPermission ? 'granted' : locationService.getPermissionStatus()
128
- );
129
- };
12
+ export function useLocation(): UseLocationResult {
13
+ const [location, setLocation] = useState<LocationData | null>(null);
14
+ const [isLoading, setIsLoading] = useState(false);
15
+ const [error, setError] = useState<LocationError | null>(null);
130
16
 
131
- if (isAvailable) {
132
- checkPermission();
133
- }
134
- }, [isAvailable]);
135
-
136
- /**
137
- * Request location permissions
138
- */
139
- const requestPermission = useCallback(async (): Promise<boolean> => {
140
- if (!isAvailable) {
141
- setError('Location services not available on this platform');
142
- setPermissionStatus('denied');
143
- return false;
144
- }
145
-
146
- try {
147
- const granted = await locationService.requestPermissions();
148
- setPermissionStatus(granted ? 'granted' : 'denied');
149
-
150
- if (!granted) {
151
- setError('Location permission denied');
152
- }
153
-
154
- return granted;
155
- } catch (err) {
156
- setError('Failed to request location permission');
157
- setPermissionStatus('denied');
158
- return false;
159
- }
160
- }, [isAvailable]);
161
-
162
- /**
163
- * Get current location with fallback strategies
164
- */
165
- const getCurrentLocation = useCallback(async (): Promise<void> => {
166
- if (!isAvailable) {
167
- setError('Location services not available on this platform');
168
- return;
169
- }
170
-
171
- setLoading(true);
172
- setError(null);
173
-
174
- try {
175
- const currentLocation = await locationService.getCurrentLocation();
176
-
177
- if (currentLocation) {
178
- setLocation(currentLocation);
17
+ const getCurrentLocation = useCallback(async () => {
18
+ setIsLoading(true);
179
19
  setError(null);
180
- } else {
181
- setError('Unable to retrieve location');
182
- }
183
- } catch (err) {
184
- setError('Failed to get location');
185
- } finally {
186
- setLoading(false);
187
- }
188
- }, [isAvailable]);
189
-
190
- /**
191
- * Get cached location
192
- */
193
- const getCached = useCallback((): LocationData | null => {
194
- return locationService.getCachedLocation();
195
- }, []);
196
-
197
- /**
198
- * Format location for display
199
- */
200
- const formatLocation = useCallback((loc: LocationData): string => {
201
- return locationService.formatLocation(loc);
202
- }, []);
203
-
204
- /**
205
- * Clear location state
206
- */
207
- const clearLocation = useCallback((): void => {
208
- setLocation(null);
209
- setError(null);
210
- }, []);
211
-
212
- return {
213
- location,
214
- loading,
215
- error,
216
- permissionStatus,
217
- hasPermission: permissionStatus === 'granted',
218
- isAvailable,
219
- requestPermission,
220
- getCurrentLocation,
221
- getCached,
222
- formatLocation,
223
- clearLocation,
224
- };
225
- };
20
+ try {
21
+ const data = await locationService.getCurrentPosition(true);
22
+ setLocation(data);
23
+ return data;
24
+ } catch (err: any) {
25
+ const errorObj = {
26
+ code: err.code || "UNKNOWN_ERROR",
27
+ message: err.message || "An unknown error occurred",
28
+ };
29
+ setError(errorObj);
30
+ return null;
31
+ } finally {
32
+ setIsLoading(false);
33
+ }
34
+ }, []);
35
+
36
+ return {
37
+ location,
38
+ isLoading,
39
+ error,
40
+ getCurrentLocation,
41
+ };
42
+ }
package/README.md DELETED
@@ -1,127 +0,0 @@
1
- # @umituz/react-native-location
2
-
3
- Device location services for React Native with GPS, permissions, caching, and reverse geocoding.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @umituz/react-native-location
9
- ```
10
-
11
- ## Peer Dependencies
12
-
13
- - `react` >= 18.2.0
14
- - `react-native` >= 0.74.0
15
- - `expo-location` *
16
- - `@react-native-async-storage/async-storage` *
17
-
18
- ## Features
19
-
20
- - ✅ Current location retrieval with GPS
21
- - ✅ Location permission management (iOS/Android)
22
- - ✅ 15-minute location caching for performance
23
- - ✅ Reverse geocoding (coordinates → address)
24
- - ✅ Multi-tier accuracy fallback (High → Balanced → Cached)
25
- - ✅ Platform-aware (iOS/Android only, no web support)
26
-
27
- ## Usage
28
-
29
- ### Using Hook (Recommended)
30
-
31
- ```typescript
32
- import { useLocation } from '@umituz/react-native-location';
33
-
34
- const MyComponent = () => {
35
- const {
36
- location,
37
- loading,
38
- hasPermission,
39
- requestPermission,
40
- getCurrentLocation,
41
- formatLocation,
42
- } = useLocation();
43
-
44
- useEffect(() => {
45
- // Auto-fetch on mount
46
- getCurrentLocation();
47
- }, []);
48
-
49
- if (loading) return <LoadingIndicator />;
50
-
51
- return (
52
- <View>
53
- {location && (
54
- <>
55
- <Text>Location: {formatLocation(location)}</Text>
56
- <Text>Lat: {location.latitude}</Text>
57
- <Text>Lng: {location.longitude}</Text>
58
- {location.accuracy && (
59
- <Text>Accuracy: {location.accuracy.toFixed(0)}m</Text>
60
- )}
61
- </>
62
- )}
63
- <Button onPress={getCurrentLocation}>
64
- Refresh Location
65
- </Button>
66
- </View>
67
- );
68
- };
69
- ```
70
-
71
- ### Using Service Directly
72
-
73
- ```typescript
74
- import { locationService } from '@umituz/react-native-location';
75
-
76
- // Check permission
77
- const hasPermission = await locationService.hasPermissions();
78
-
79
- // Request permission if needed
80
- if (!hasPermission) {
81
- const granted = await locationService.requestPermissions();
82
- }
83
-
84
- // Get current location
85
- const location = await locationService.getCurrentLocation();
86
- if (location) {
87
- console.log(locationService.formatLocation(location));
88
- }
89
- ```
90
-
91
- ## Platform Support
92
-
93
- - ✅ iOS: Full support (GPS, permissions, geocoding)
94
- - ✅ Android: Full support (GPS, permissions, geocoding)
95
- - ❌ Web: Not supported (returns null)
96
-
97
- ## Permission Flow
98
-
99
- 1. Check: `hasPermissions()` → true/false
100
- 2. Request: `requestPermissions()` → true/false
101
- 3. Retrieve: `getCurrentLocation()` → LocationData | null
102
-
103
- ## Fallback Strategy
104
-
105
- 1. High accuracy GPS (15s timeout)
106
- 2. Balanced accuracy GPS (8s timeout)
107
- 3. Cached location (15min cache)
108
- 4. null (no location available)
109
-
110
- ## Caching
111
-
112
- - Duration: 15 minutes
113
- - Storage: AsyncStorage
114
- - Auto-refresh: On `getCurrentLocation()` if expired
115
-
116
- ## Hooks
117
-
118
- - `useLocation()` - Main hook for location operations
119
-
120
- ## Services
121
-
122
- - `locationService` - Direct service access for location operations
123
-
124
- ## License
125
-
126
- MIT
127
-