diff --git a/Docs/Android_development.md b/Docs/Android_development.md new file mode 100644 index 000000000..231f24a86 --- /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 cc6516521..3cb200310 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 b48b9445c..faa3d1f07 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 9e26dfeeb..7a73a41bf 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 8da763e56..276d69a20 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 9e26dfeeb..7a73a41bf 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 bcf392d9b..63e3842e2 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 d697c01ab..601648178 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 7b5b7a0ec..352a8d617 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 c4f0d96e3..92c37ff8d 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 1cdd5d134..be3a26f2d 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 9e26dfeeb..7a73a41bf 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 bbe70782a..e5f5d1690 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 266a9c76c..0af50180d 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 d6f630728..f6132f5f4 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 afa0ec7a3..5b2e0150a 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 06d05f723..40aafdbdc 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 f1157d0af..77c0f971e 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 f6477320c..637140a39 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 9681c01da..939b5db4c 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 e1f8ccaef..64d85e93d 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 c5013c067..030291a66 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 68b8ad946..3c947bdd2 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