Skip to content

Commit bd32159

Browse files
feat: fully working auto-location feature
1 parent 47798c7 commit bd32159

File tree

2 files changed

+190
-10
lines changed

2 files changed

+190
-10
lines changed

config/common.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"platform": "web",
33

44
"url": {
5-
"prose_help": "https://help.prose.org"
5+
"prose_help": "https://help.prose.org",
6+
"nominatim_geocoder": "https://nominatim.openstreetmap.org"
67
},
78

89
"context": {

src/components/popups/sidebar/EditProfileProfile.vue

+188-9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { JID } from "@prose-im/prose-sdk-js";
2626
import { countries } from "crisp-countries-languages";
2727

2828
// PROJECT: COMPONENTS
29+
import BaseAlert from "@/components/base/BaseAlert.vue";
2930
import BaseFlag from "@/components/base/BaseFlag.vue";
3031
import {
3132
default as FormSettingsEditor,
@@ -43,6 +44,9 @@ import {
4344
// PROJECT: POPUPS
4445
import { FormProfile as ProfileFormProfile } from "@/popups/sidebar/EditProfile.vue";
4546

47+
// PROJECT: COMMONS
48+
import CONFIG from "@/commons/config";
49+
4650
// ENUMERATIONS
4751
enum GeolocationPermission {
4852
// Not yet allowed permission.
@@ -51,10 +55,34 @@ enum GeolocationPermission {
5155
Disallowed = "disallowed",
5256
// Allowed permission.
5357
Allowed = "allowed",
58+
// FailedToObtain permission.
59+
FailedToObtain = "failed-to-obtain",
5460
// Unknown permission.
5561
Unknown = "unknown"
5662
}
5763

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+
5886
export default {
5987
name: "EditProfileProfile",
6088

@@ -98,7 +126,9 @@ export default {
98126

99127
// --> STATE <--
100128

101-
geolocationPermission: GeolocationPermission.Unknown
129+
geolocationPermission: GeolocationPermission.Unknown,
130+
131+
isRefreshingGeolocationPosition: false
102132
};
103133
},
104134

@@ -211,17 +241,28 @@ export default {
211241
{
212242
type: FormFieldsetControlActionType.Button,
213243

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
217258
}
218259
]
219260
: undefined
220261
}
221262
],
222263

223264
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.",
225266
"Note that geolocation permissions are required for automatic mode."
226267
],
227268

@@ -236,15 +277,15 @@ export default {
236277
label: string;
237278
color: string;
238279
emphasis?: boolean;
239-
action?: boolean;
280+
action?: GeolocationAction;
240281
} {
241282
switch (this.geolocationPermission) {
242283
case GeolocationPermission.NotYetAllowed: {
243284
return {
244285
label: "Not yet allowed",
245286
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
248289
};
249290
}
250291

@@ -259,7 +300,19 @@ export default {
259300
case GeolocationPermission.Allowed: {
260301
return {
261302
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
263316
};
264317
}
265318

@@ -307,6 +360,132 @@ export default {
307360

308361
return GeolocationPermission.Unknown;
309362
}
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+
}
310489
}
311490
}
312491
};

0 commit comments

Comments
 (0)