diff --git a/Docs/Android_development.md b/Docs/Android_development.md new file mode 100644 index 0000000000..231f24a863 --- /dev/null +++ b/Docs/Android_development.md @@ -0,0 +1,12 @@ +# Creating an APK from the code + +We are using capacitor. This is a tool which packages some files into an Android shell. + +## Developing + +1. Build all the necessary files. + a. If no layer/theme changes were made, `npm run build` is sufficient + b. Otherwise, run `npm run prepare-deploy`. +2. All the web assets will now be in `dist/` +3. Run `scripts/prepareAndroid.sh` +4. Switch to Android Studio, open the subproject "Android" in it diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index cc6516521f..3cb2003104 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -3238,7 +3238,7 @@ }, { "id": "name", - "question":{ + "question": { "en": "What is the name of this place?" }, "render": { diff --git a/langs/da.json b/langs/da.json index b48b9445c1..faa3d1f07e 100644 --- a/langs/da.json +++ b/langs/da.json @@ -460,7 +460,8 @@ "activateButton": "Hjælp med at oversætte MapComplete", "missing": "{count} uoversatte strenge" }, - "userinfo": {}, + "userinfo": { + }, "validation": { "color": { "description": "En farve eller hex-kode" diff --git a/langs/el.json b/langs/el.json index 9e26dfeeb6..7a73a41bfd 100644 --- a/langs/el.json +++ b/langs/el.json @@ -1 +1,2 @@ -{} \ No newline at end of file +{ +} \ No newline at end of file diff --git a/langs/fil.json b/langs/fil.json index 8da763e562..276d69a204 100644 --- a/langs/fil.json +++ b/langs/fil.json @@ -94,7 +94,8 @@ "question_opinion": "Kamusta ang iyong karanasan?", "reviewPlaceholder": "Ilarawan ang iyong karanasan…" }, - "translations": {}, + "translations": { + }, "unknown": { "clear": "Tanggalin ang sagot" }, diff --git a/langs/he_IL.json b/langs/he_IL.json index 9e26dfeeb6..7a73a41bfd 100644 --- a/langs/he_IL.json +++ b/langs/he_IL.json @@ -1 +1,2 @@ -{} \ No newline at end of file +{ +} \ No newline at end of file diff --git a/langs/id.json b/langs/id.json index bcf392d9ba..63e3842e27 100644 --- a/langs/id.json +++ b/langs/id.json @@ -150,7 +150,8 @@ "split": { "cancel": "Batal" }, - "translations": {}, + "translations": { + }, "validation": { "date": { "description": "Tanggal, dimulai dari tahun" diff --git a/langs/layers/en.json b/langs/layers/en.json index d697c01abc..601648178a 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -959,6 +959,30 @@ "render": "BBQ" } }, + "beehive": { + "description": "Layer showing beehives", + "name": "Beehives", + "presets": { + "0": { + "title": "a beehive" + } + }, + "tagRenderings": { + "capacity": { + "freeform": { + "placeholder": "Number of beehives" + }, + "mappings": { + "0": { + "then": "There is 1 beehive" + } + }, + "question": "How many beehives are there?", + "render": "There are {capacity} beehives" + } + }, + "title": "Beehive" + }, "bench": { "description": "A bench is a wooden, metal, stone, … surface where a human can sit. This layers visualises them and asks a few questions about them.", "filter": { @@ -6358,6 +6382,16 @@ "render": "Information board" } }, + "insect_hotel": { + "description": "Layer showing insect hotels", + "name": "Insect Hotels", + "presets": { + "0": { + "title": "an insect hotel" + } + }, + "title": "Insect Hotel" + }, "item_with_image": { "name": "Items with at least one image", "title": { @@ -8682,6 +8716,9 @@ "render": "This elevator goes to floors {level}" } }, + "name": { + "question": "What is the name of this place?" + }, "nothing_known": { "render": { "special": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 7b5b7a0ec7..352a8d6174 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -5322,6 +5322,16 @@ "render": "Informatiebord" } }, + "insect_hotel": { + "description": "Laag met insectenhotels", + "name": "Insectenhotels", + "presets": { + "0": { + "title": "een insectenhotel" + } + }, + "title": "Insectenhotel" + }, "kerbs": { "description": "Een laag met stoepranden.", "filter": { diff --git a/langs/nb_NO.json b/langs/nb_NO.json index c4f0d96e38..92c37ff8d2 100644 --- a/langs/nb_NO.json +++ b/langs/nb_NO.json @@ -274,7 +274,8 @@ "importInspector": { "title": "Inspiser og håndter importnotater" }, - "importLayer": {}, + "importLayer": { + }, "index": { "intro": "MapComplete er en OpenStreetMap-viser og redigerer, som viser deg info om funksjoner for et gitt tema og tillater oppdatering av det.", "logIn": "Logg inn for å vise tema du har besøkt tidligere", @@ -369,7 +370,8 @@ "activateButton": "Bistå oversettelsen av MapComplete", "missing": "{count} uoversatte strenger" }, - "userinfo": {}, + "userinfo": { + }, "validation": { "color": { "description": "En farge eller heksadesimal kode" diff --git a/langs/pa_PK.json b/langs/pa_PK.json index 1cdd5d1342..be3a26f2d6 100644 --- a/langs/pa_PK.json +++ b/langs/pa_PK.json @@ -53,7 +53,8 @@ "search": "ستھتیاں وچ کھوجو", "searching": "کھوجیا جا رہا اے۔ ۔ ۔" }, - "sharescreen": {}, + "sharescreen": { + }, "weekdays": { "abbreviations": { "friday": "جـ", diff --git a/langs/ro.json b/langs/ro.json index 9e26dfeeb6..7a73a41bfd 100644 --- a/langs/ro.json +++ b/langs/ro.json @@ -1 +1,2 @@ -{} \ No newline at end of file +{ +} \ No newline at end of file diff --git a/langs/themes/en.json b/langs/themes/en.json index bbe70782a6..e5f5d16908 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -488,6 +488,11 @@ "override": { "=name": "Sport places without etymology information" } + }, + "8": { + "override": { + "=name": "Parks without etymology information" + } } }, "shortDescription": "What is the origin of a toponym?", @@ -721,6 +726,10 @@ "description": "On this map, publicly accessible indoor places are shown", "title": "Indoors" }, + "insects": { + "description": "Insect hotels provide shelter for insects.", + "title": "Insect Hotels" + }, "items_with_image": { "description": "A map showing all items on OSM which have an image. This theme is a very bad fit for MapComplete as someone is not able to directly add a picture. However, this theme is mostly here to include this all into the database, which'll allow this to quickly fetch images nearby for other features", "title": "All items with images" diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 266a9c76c1..0af50180df 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -777,6 +777,10 @@ "description": "Op deze kaart worden publiek toegankelijke binnenruimtes getoond", "title": "Binnenruimtes" }, + "insects": { + "description": "Insectenhotels bieden onderdak aan insecten.", + "title": "Insectenhotels" + }, "items_with_image": { "description": "Een kaart die alle items op OSM toont die een afbeelding hebben. Dit thema past heel slecht bij MapComplete omdat het niet mogelijk is een afbeelding toe te voegen. Dit thema is er vooral om alles in de database op te nemen, waardoor het snel afbeeldingen in de buurt kan ophalen voor andere functies", "title": "Alle items met afbeeldingen" diff --git a/langs/uk.json b/langs/uk.json index d6f630728b..f6132f5f41 100644 --- a/langs/uk.json +++ b/langs/uk.json @@ -532,7 +532,8 @@ } } }, - "importLayer": {}, + "importLayer": { + }, "index": { "about": "Про MapComplete", "intro": "Тематичні мапи, до створення яких ви можете долучитися", @@ -591,7 +592,8 @@ "removedKeys": "Наступні ключі будуть видалені:", "title": "Позначити як невідомий?" }, - "userinfo": {}, + "userinfo": { + }, "validation": { "opening_hours": { "description": "Години роботи" diff --git a/package-lock.json b/package-lock.json index afa0ec7a39..5b2e0150a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@capacitor/android": "^6.1.2", "@capacitor/assets": "^3.0.5", "@capacitor/core": "^6.1.2", - "@capacitor/geolocation": "^6.0.1", "@comunica/core": "^3.0.1", "@comunica/query-sparql": "^3.0.1", "@comunica/query-sparql-link-traversal": "^0.3.0", @@ -2041,14 +2040,6 @@ "tslib": "^2.1.0" } }, - "node_modules/@capacitor/geolocation": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@capacitor/geolocation/-/geolocation-6.0.1.tgz", - "integrity": "sha512-QOkIrSzG6E0vD2MF3gZmtuILQiuVro4LGPjqrUjCzhX10zl/4lx6bq4T+hj2YLUmMUnCiV1hWTOJHcpdVRMz7w==", - "peerDependencies": { - "@capacitor/core": "^6.0.0" - } - }, "node_modules/@colors/colors": { "version": "1.6.0", "license": "MIT", @@ -24098,12 +24089,6 @@ "tslib": "^2.1.0" } }, - "@capacitor/geolocation": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@capacitor/geolocation/-/geolocation-6.0.1.tgz", - "integrity": "sha512-QOkIrSzG6E0vD2MF3gZmtuILQiuVro4LGPjqrUjCzhX10zl/4lx6bq4T+hj2YLUmMUnCiV1hWTOJHcpdVRMz7w==", - "requires": {} - }, "@colors/colors": { "version": "1.6.0" }, diff --git a/package.json b/package.json index 06d05f723d..40aafdbdc3 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,10 @@ "generate:summaryCache": "vite-node scripts/generateSummaryTileCache.ts", "create:database": "vite-node scripts/osm2pgsql/createNewDatabase.ts", "delete:database:old": "vite-node scripts/osm2pgsql/deleteOldDbs.ts", - "upload:panoramax": "vite-node scripts/ImgurToPanoramax.ts # && josm imgur_to_panoramax.osc" + "upload:panoramax": "vite-node scripts/ImgurToPanoramax.ts # && josm imgur_to_panoramax.osc", + + "#": "Android development" + }, "keywords": [ "OpenStreetMap", @@ -163,7 +166,6 @@ "@capacitor/android": "^6.1.2", "@capacitor/assets": "^3.0.5", "@capacitor/core": "^6.1.2", - "@capacitor/geolocation": "^6.0.1", "@comunica/core": "^3.0.1", "@comunica/query-sparql": "^3.0.1", "@comunica/query-sparql-link-traversal": "^0.3.0", diff --git a/src/Logic/State/GeoLocationState.ts b/src/Logic/State/GeoLocationState.ts index f1157d0af4..77c0f971eb 100644 --- a/src/Logic/State/GeoLocationState.ts +++ b/src/Logic/State/GeoLocationState.ts @@ -3,7 +3,6 @@ import { LocalStorageSource } from "../Web/LocalStorageSource" import { QueryParameters } from "../Web/QueryParameters" import { Translation } from "../../UI/i18n/Translation" import Translations from "../../UI/i18n/Translations" -import { Geolocation } from "@capacitor/geolocation" export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied" @@ -179,20 +178,19 @@ export class GeoLocationState { this.permission.setData("requested") try { const status = await navigator?.permissions?.query({ name: "geolocation" }) - const self = this console.log("Got geolocation state", status.state) if (status.state === "granted" || status.state === "denied") { - self.permission.setData(status.state) - self.startWatching() + this.permission.setData(status.state) + this.startWatching() return } status.addEventListener("change", () => { - self.permission.setData(status.state) + this.permission.setData(status.state) }) // The code above might have reset it to 'prompt', but we _did_ request permission! this.permission.setData("requested") // We _must_ call 'startWatching', as that is the actual trigger for the popup... - self.startWatching() + this.startWatching() } catch (e) { console.error("Could not get permission:", e) } @@ -203,46 +201,29 @@ export class GeoLocationState { * @private */ private async startWatching() { - console.log("Starts watching", navigator.geolocation, Geolocation) - const self = this - try { - await Geolocation.requestPermissions({ permissions: ["location"] }) - console.log("Requested permission") - } catch (e) { - // pass - } - try { - await Geolocation.watchPosition( - { - enableHighAccuracy: true, - maximumAge: 120000, - }, - (position: GeolocationPosition, error: GeolocationPositionError) => { - if (error) { - if (error.code === 2 || error.code === 3) { - self._gpsAvailable.set(false) - return - } - self._gpsAvailable.set(true) // We go back to the default assumption that the location is physically available - if (error.code === 1) { - self.permission.set("denied") - self._grantedThisSession.setData(false) - return - } - console.warn("Could not get location with navigator.geolocation due to", error) - } - - - console.log("Got position:", position, JSON.stringify(position)) - if (!position) { - return - } - this._gpsAvailable.set(true) - this.currentGPSLocation.setData(position.coords) - this._previousLocationGrant.setData(true) - }) - } catch (e) { - console.error("Could not get geolocation due to", e) - } + navigator.geolocation.watchPosition( + (position: GeolocationPosition) => { + this._gpsAvailable.set(true) + this.currentGPSLocation.setData(position.coords) + this._previousLocationGrant.setData(true) + }, + (e: GeolocationPositionError) => { + if (e.code === 2 || e.code === 3) { + this._gpsAvailable.set(false) + return + } + this._gpsAvailable.set(true) // We go back to the default assumption that the location is physically available + if (e.code === 1) { + this.permission.set("denied") + this._grantedThisSession.setData(false) + return + } + console.warn("Could not get location with navigator.geolocation due to", e) + }, + { + enableHighAccuracy: true, + } + ) } + } diff --git a/src/Logic/Web/AndroidPolyfill.ts b/src/Logic/Web/AndroidPolyfill.ts index f6477320c5..637140a394 100644 --- a/src/Logic/Web/AndroidPolyfill.ts +++ b/src/Logic/Web/AndroidPolyfill.ts @@ -3,36 +3,62 @@ * If this is successful, it will patch some webAPIs */ import { registerPlugin } from "@capacitor/core" - -export class AndroidPolyfill { - private readonly databridgePlugin: DatabridgePlugin - - constructor() { - this.databridgePlugin = registerPlugin("Databridge", { - web: () => { - return { - async request(options: { key: string }): Promise<{ value: string }> { - return { value: "web" } - }, - } - }, - }) - - } - - public async init(){ - const shell = await this.databridgePlugin.request({ key: "meta" }) - if(shell.value === "web"){ - console.log("Not initing Android polyfill; web detected") - return - } - console.log("Detected shell:", shell.value) - } - -} +import { UIEventSource } from "../UIEventSource" export interface DatabridgePlugin { request(options: { key: string }): Promise<{ value: string }>; } -new AndroidPolyfill().init() +const DatabridgePluginSingleton = registerPlugin("Databridge", { + web: () => { + return { + async request(options: { key: string }): Promise<{ value: string }> { + return { value: "web" } + }, + } + }, +}) + +export class AndroidPolyfill { + private readonly databridgePlugin: DatabridgePlugin = DatabridgePluginSingleton + + /** + * Registers 'navigator.' + * @private + */ + private backfillGeolocation(databridgePlugin: DatabridgePlugin) { + const origQueryFunc = navigator?.permissions?.query + navigator.permissions.query = async (descr: PermissionDescriptor) => { + if (descr.name === "geolocation") { + console.log("Got a geolocation permission request") + const src = UIEventSource.FromPromise(databridgePlugin.request({ key: "location:request-permission" })) + + return { + state: undefined, + addEventListener(key: "change", f: (value: "granted" | "denied") => void) { + src.addCallbackAndRunD(v => { + const content = <"granted" | "denied">v.value + f(content) + return true + }) + }, + } + } + if (origQueryFunc) { + return await origQueryFunc(descr) + } + } + } + + public async init() { + console.log("Sniffing shell version") + const shell = await this.databridgePlugin.request({ key: "meta" }) + if (shell.value === "web") { + console.log("Not initing Android polyfill as not in a shell; web detected") + return + } + console.log("Detected shell:", shell.value) + this.backfillGeolocation(this.databridgePlugin) + } + +} diff --git a/src/UI/AllThemesGui.svelte b/src/UI/AllThemesGui.svelte index 9681c01da1..939b5db4cf 100644 --- a/src/UI/AllThemesGui.svelte +++ b/src/UI/AllThemesGui.svelte @@ -25,7 +25,8 @@ import ThemeSearch from "../Logic/Search/ThemeSearch" import SearchUtils from "../Logic/Search/SearchUtils" import ChevronDoubleRight from "@babeard/svelte-heroicons/mini/ChevronDoubleRight" - + import { AndroidPolyfill } from "../Logic/Web/AndroidPolyfill" + new AndroidPolyfill().init().then(() => console.log("Android polyfill setup completed")) const featureSwitches = new OsmConnectionFeatureSwitches() const osmConnection = new OsmConnection({ fakeUser: featureSwitches.featureSwitchFakeUser.data, diff --git a/src/assets/bing.json b/src/assets/bing.json index e1f8ccaef7..64d85e93d2 100644 --- a/src/assets/bing.json +++ b/src/assets/bing.json @@ -1 +1 @@ -{"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t3.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14738&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null} \ No newline at end of file +{"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t1.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14860&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c5013c0675..030291a668 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import { SubtleButton } from "./UI/Base/SubtleButton" import { Utils } from "./Utils" import Constants from "./Models/Constants" import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray" +import { AndroidPolyfill } from "./Logic/Web/AndroidPolyfill" function webgl_support() { try { @@ -48,6 +49,7 @@ async function main() { if (!webgl_support()) { throw "WebGL is not supported or not enabled. This is essential for MapComplete to function, please enable this." } + new AndroidPolyfill().init().then(() => console.log("Android polyfill setup completed")) const [theme, availableLayers] = await Promise.all([ DetermineTheme.getTheme(), await getAvailableLayers(), diff --git a/src/index_theme.ts.template b/src/index_theme.ts.template index 68b8ad9462..3c947bdd21 100644 --- a/src/index_theme.ts.template +++ b/src/index_theme.ts.template @@ -45,6 +45,7 @@ async function main() { if (!webgl_support()) { new FixedUiElement("WebGL is not supported or not enabled. This is essential for MapComplete to function, please enable this.").SetClass("block alert").AttachTo("maindiv") }else{ + new AndroidPolyfill().init().then(() => console.log("Android polyfill setup completed")) const availableLayers = await getAvailableLayers() MetaTagging.setThemeMetatagging(new ThemeMetaTagging()) // LAYOUT.ADD_LAYERS