@@ -26,6 +26,7 @@ import { JID } from "@prose-im/prose-sdk-js";
26
26
import { countries } from "crisp-countries-languages";
27
27
28
28
// PROJECT: COMPONENTS
29
+ import BaseAlert from "@/components/base/BaseAlert.vue";
29
30
import BaseFlag from "@/components/base/BaseFlag.vue";
30
31
import {
31
32
default as FormSettingsEditor,
@@ -43,6 +44,9 @@ import {
43
44
// PROJECT: POPUPS
44
45
import { FormProfile as ProfileFormProfile } from "@/popups/sidebar/EditProfile.vue";
45
46
47
+ // PROJECT: COMMONS
48
+ import CONFIG from "@/commons/config";
49
+
46
50
// ENUMERATIONS
47
51
enum GeolocationPermission {
48
52
// Not yet allowed permission.
@@ -51,10 +55,34 @@ enum GeolocationPermission {
51
55
Disallowed = "disallowed",
52
56
// Allowed permission.
53
57
Allowed = "allowed",
58
+ // FailedToObtain permission.
59
+ FailedToObtain = "failed-to-obtain",
54
60
// Unknown permission.
55
61
Unknown = "unknown"
56
62
}
57
63
64
+ enum GeolocationAction {
65
+ // Allow action.
66
+ Allow = "allow",
67
+ // Refresh action.
68
+ Refresh = "refresh"
69
+ }
70
+
71
+ // INTERFACES
72
+ interface GeolocationPositionCoordinates {
73
+ latitude: number;
74
+ longitude: number;
75
+ }
76
+
77
+ interface GeolocationPositionAddress {
78
+ cityName: string;
79
+ countryCode: string;
80
+ }
81
+
82
+ // CONSTANTS
83
+ const GEOLOCATION_ACQUIRE_STALE_AGE = 3600000; // 1 hour
84
+ const GEOLOCATION_ACQUIRE_TIMEOUT = 10000; // 10 seconds
85
+
58
86
export default {
59
87
name: "EditProfileProfile",
60
88
@@ -98,7 +126,9 @@ export default {
98
126
99
127
// --> STATE <--
100
128
101
- geolocationPermission: GeolocationPermission.Unknown
129
+ geolocationPermission: GeolocationPermission.Unknown,
130
+
131
+ isRefreshingGeolocationPosition: false
102
132
};
103
133
},
104
134
@@ -211,17 +241,28 @@ export default {
211
241
{
212
242
type: FormFieldsetControlActionType.Button,
213
243
214
- data: {
215
- text: "Allow"
216
- } as FormFieldsetControlActionDataButton
244
+ data: (this.locationPermissionDetails.action ===
245
+ GeolocationAction.Refresh
246
+ ? {
247
+ text: this.isRefreshingGeolocationPosition
248
+ ? "Refreshing"
249
+ : "Refresh location",
250
+
251
+ disabled: this.isRefreshingGeolocationPosition,
252
+ click: this.onFieldsetLocationPermissionRefreshClick
253
+ }
254
+ : {
255
+ text: "Allow",
256
+ click: this.onFieldsetLocationPermissionAllowClick
257
+ }) as FormFieldsetControlActionDataButton
217
258
}
218
259
]
219
260
: undefined
220
261
}
221
262
],
222
263
223
264
notes: [
224
- "You can opt-in to automatic location updates based on your last used device location . It is handy if you travel a lot, and would like this to be auto-managed . Your current city and country will be shared, not your exact GPS location.",
265
+ "You can opt-in to automatic location detection . It is handy if you travel a lot, and would like to easily update your location in a single click . Your current city and country will be shared, not your exact GPS location.",
225
266
"Note that geolocation permissions are required for automatic mode."
226
267
],
227
268
@@ -236,15 +277,15 @@ export default {
236
277
label: string;
237
278
color: string;
238
279
emphasis?: boolean;
239
- action?: boolean ;
280
+ action?: GeolocationAction ;
240
281
} {
241
282
switch (this.geolocationPermission) {
242
283
case GeolocationPermission.NotYetAllowed: {
243
284
return {
244
285
label: "Not yet allowed",
245
286
color: this.form.locationAutodetect.inner ? "orange" : "grey",
246
- emphasis: this.form.locationAutodetect.inner || false
247
- // action: true -- TODO: add action
287
+ emphasis: this.form.locationAutodetect.inner || false,
288
+ action: GeolocationAction.Allow
248
289
};
249
290
}
250
291
@@ -259,7 +300,19 @@ export default {
259
300
case GeolocationPermission.Allowed: {
260
301
return {
261
302
label: "Allowed",
262
- color: this.form.locationAutodetect.inner ? "green" : "grey"
303
+ color: this.form.locationAutodetect.inner ? "green" : "grey",
304
+
305
+ action: this.form.locationAutodetect.inner
306
+ ? GeolocationAction.Refresh
307
+ : undefined
308
+ };
309
+ }
310
+
311
+ case GeolocationPermission.FailedToObtain: {
312
+ return {
313
+ label: "Failed to obtain!",
314
+ color: "red",
315
+ emphasis: true
263
316
};
264
317
}
265
318
@@ -307,6 +360,132 @@ export default {
307
360
308
361
return GeolocationPermission.Unknown;
309
362
}
363
+ },
364
+
365
+ async obtainGeolocationPositionCoordinates(): Promise<GeolocationPositionCoordinates | null> {
366
+ return new Promise((resolve, reject) => {
367
+ try {
368
+ navigator.geolocation.getCurrentPosition(
369
+ position => {
370
+ // Resolve with success
371
+ resolve({
372
+ latitude: position.coords.latitude,
373
+ longitude: position.coords.longitude
374
+ });
375
+ },
376
+
377
+ () => {
378
+ // Reject with error
379
+ resolve(null);
380
+ },
381
+
382
+ {
383
+ timeout: GEOLOCATION_ACQUIRE_TIMEOUT,
384
+ maximumAge: GEOLOCATION_ACQUIRE_STALE_AGE
385
+ }
386
+ );
387
+ } catch (error) {
388
+ // Reject with error
389
+ reject(error);
390
+ }
391
+ });
392
+ },
393
+
394
+ async obtainGeolocationPositionAddress(
395
+ coordinates: GeolocationPositionCoordinates
396
+ ): Promise<GeolocationPositionAddress> {
397
+ // Geocode obtained coordinates (to a city and a country)
398
+ const geocoderQuery = new URLSearchParams();
399
+
400
+ geocoderQuery.append("format", "jsonv2");
401
+ geocoderQuery.append("lat", `${coordinates.latitude}`);
402
+ geocoderQuery.append("lon", `${coordinates.longitude}`);
403
+
404
+ const geocoderResponse = await fetch(
405
+ `${CONFIG.url.nominatim_geocoder}/reverse?${geocoderQuery}`,
406
+ {
407
+ mode: "cors"
408
+ }
409
+ );
410
+
411
+ if (geocoderResponse.ok !== true) {
412
+ throw new Error("Geocoding request failed");
413
+ }
414
+
415
+ const geocoding = (await geocoderResponse.json()) || {};
416
+
417
+ if (geocoding.address?.city && geocoding.address?.country_code) {
418
+ return {
419
+ cityName: geocoding.address.city,
420
+ countryCode: geocoding.address.country_code.toUpperCase()
421
+ };
422
+ }
423
+
424
+ throw new Error("Geocoding got no result");
425
+ },
426
+
427
+ // --> EVENT LISTENERS <--
428
+
429
+ async onFieldsetLocationPermissionAllowClick(): Promise<void> {
430
+ try {
431
+ // Obtain geolocation position (this will request for permission if \
432
+ // needed)
433
+ const coordinates = await this.obtainGeolocationPositionCoordinates();
434
+
435
+ // Mark as allowed or disallowed?
436
+ this.geolocationPermission =
437
+ coordinates === null
438
+ ? GeolocationPermission.Disallowed
439
+ : GeolocationPermission.Allowed;
440
+ } catch (error) {
441
+ this.$log.error("Failed obtaining geolocation allow permission", error);
442
+
443
+ // Show warning alert
444
+ BaseAlert.warning("Location failed", "Could not obtain permissions");
445
+
446
+ // Mark as 'failed to obtain'
447
+ this.geolocationPermission = GeolocationPermission.FailedToObtain;
448
+ }
449
+ },
450
+
451
+ async onFieldsetLocationPermissionRefreshClick(): Promise<void> {
452
+ if (this.isRefreshingGeolocationPosition !== true) {
453
+ this.isRefreshingGeolocationPosition = true;
454
+
455
+ try {
456
+ // Obtain geolocation position
457
+ const coordinates = await this.obtainGeolocationPositionCoordinates();
458
+
459
+ // No geolocation position obtained?
460
+ if (coordinates === null) {
461
+ throw new Error("Coordinates are empty");
462
+ }
463
+
464
+ // Geocode obtained coordinates (to a city and a country)
465
+ const address = await this.obtainGeolocationPositionAddress(
466
+ coordinates
467
+ );
468
+
469
+ // Update user city and country (in form)
470
+ const form = this.form;
471
+
472
+ form.locationCity.inner = address.cityName;
473
+ form.locationCountry.inner = address.countryCode;
474
+
475
+ // Show success alert
476
+ BaseAlert.success(
477
+ "Location refreshed",
478
+ "Please save your profile to confirm"
479
+ );
480
+ } catch (error) {
481
+ this.$log.error("Failed obtaining geolocation fresh position", error);
482
+
483
+ // Show warning alert
484
+ BaseAlert.warning("Location failed", "Could not geolocate you!");
485
+ } finally {
486
+ this.isRefreshingGeolocationPosition = false;
487
+ }
488
+ }
310
489
}
311
490
}
312
491
};
0 commit comments