diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0ee9ff3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing to Blobling + +Ideas, bug reports and pull requests are welcome. Ideas and the roadmap live in +the [issues](https://github.com/mLampir/blobling/issues); how the game works is +in [docs/gameplay.md](docs/gameplay.md), and the notes in [CLAUDE.md](CLAUDE.md) +list the rules the project keeps (English only, texts in the language files, +sprites from the script, and so on). + +Before you open a pull request, run the tests and the type check: + +```bash +npm test && npm run check +``` + +## The license of your contribution + +Blobling is licensed under the GNU General Public License, version 2 or (at +your option) any later version ([LICENSE](LICENSE)). The phone app is also +offered in Apple's App Store and Google Play, whose terms of use add +restrictions the GPL alone does not allow. So the project's own code carries +one additional permission: + +> **App store permission.** As an additional permission to the GNU General +> Public License, the copyright holders of Blobling allow Lampir +> () to distribute the Blobling app, built from this +> code, through app stores (such as Apple's App Store and Google Play) under +> the terms of use those stores require, even where those terms restrict the +> recipients' rights under the GPL. The source code stays available under the +> GPL in this repository. + +**By opening a pull request, you agree that your contribution is licensed +under the GPL version 2 or later together with this permission**, and that you +have the right to license it that way (it is your own work, or under a license +that allows it). Please don't submit code you copied from elsewhere unless you +say where it comes from and under which license. + +The sprites in `app/pet/assets/` are under their own license +([app/pet/assets/LICENSE.txt](app/pet/assets/LICENSE.txt)). diff --git a/README.md b/README.md index ea72285..d061dfb 100644 --- a/README.md +++ b/README.md @@ -483,6 +483,7 @@ npm run package -- --version v1.2.0 --repo me/fork # also dist/install.sh for | Path | Purpose | |---|---| | `setup.sh` | Sets up the Pi completely (`--app`: update the app only) | +| `CONTRIBUTING.md` | How to contribute, and the license (with the app store permission) a contribution comes under | | `core/` | The pet: game logic, scene and sprites (`src/`) and their tests (`test/`). It runs anywhere and knows nothing about files or the panel; `api.ts` is the pet's JSON API, `weather.ts` asks Open-Meteo, `notify.ts` sees its next need coming for the app's notifications | | `pi/src/main.ts` | Entry point on the Pi: panel driver, display loop, web server | | `pi/src/display.ts` | The display loop (pet, off, brightness, night mode) | @@ -495,7 +496,7 @@ npm run package -- --version v1.2.0 --repo me/fork # also dist/install.sh for | `app/android/`, `app/ios/`, `capacitor.config.json` | The app's projects for the stores (Capacitor); `tools/app_icons.ts` draws their icons | | `app/templates/` | The web interface (settings page) | | `app/locales/` | All texts of the interface, one file per language | -| `docs/privacy.md`, `docs/support.md` (each with a `.de.md`), `docs/_config.yml` | The app's privacy policy and support page for the stores, and the settings of the GitHub Pages site that serves them | +| `docs/privacy.md`, `docs/support.md` (each with a `.de.md`), `docs/licenses.md`, `docs/_config.yml` | The app's privacy policy, support page and the licenses of the parts it is built with, linked from *More* in the app, and the settings of the GitHub Pages site that serves them | | `app/pet/assets/` | The sprite sheets | | `app/pet/sounds/` | The app's sounds (WAV), written by `npm run sounds` from the recipes in `tools/sfx.ts`; don't edit them by hand | | `systemd/` | The service, and the update unit the button starts | @@ -519,7 +520,8 @@ Copyright (C) 2026 Lampir () - **Code:** GNU General Public License, version 2 or (at your option) any later version, see [LICENSE](LICENSE). The project comes without any - warranty. + warranty. Contributions come with an additional permission that lets the + app be offered in the app stores, see [CONTRIBUTING.md](CONTRIBUTING.md). - **Sprites** in `app/pet/assets/`: [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/), see [app/pet/assets/LICENSE.txt](app/pet/assets/LICENSE.txt). Use, change and diff --git a/app/android/app/capacitor.build.gradle b/app/android/app/capacitor.build.gradle index 3ff4091..9d12fcc 100644 --- a/app/android/app/capacitor.build.gradle +++ b/app/android/app/capacitor.build.gradle @@ -10,6 +10,7 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { implementation project(':capacitor-filesystem') + implementation project(':capacitor-geolocation') implementation project(':capacitor-haptics') implementation project(':capacitor-local-notifications') diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index e2dd896..16bf6d2 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -7,11 +7,15 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:usesCleartextTraffic="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:localeConfig="@xml/locales_config"> + - + - diff --git a/app/android/app/src/main/res/xml/locales_config.xml b/app/android/app/src/main/res/xml/locales_config.xml new file mode 100644 index 0000000..fb754f9 --- /dev/null +++ b/app/android/app/src/main/res/xml/locales_config.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/android/capacitor.settings.gradle b/app/android/capacitor.settings.gradle index e129225..10007e5 100644 --- a/app/android/capacitor.settings.gradle +++ b/app/android/capacitor.settings.gradle @@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../../node_modules/@capacit include ':capacitor-filesystem' project(':capacitor-filesystem').projectDir = new File('../../node_modules/@capacitor/filesystem/android') +include ':capacitor-geolocation' +project(':capacitor-geolocation').projectDir = new File('../../node_modules/@capacitor/geolocation/android') + include ':capacitor-haptics' project(':capacitor-haptics').projectDir = new File('../../node_modules/@capacitor/haptics/android') diff --git a/app/ios/App/App.xcodeproj/project.pbxproj b/app/ios/App/App.xcodeproj/project.pbxproj index 7dd74a3..659c492 100644 --- a/app/ios/App/App.xcodeproj/project.pbxproj +++ b/app/ios/App/App.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; 9582B6832FE993A70072D4E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */; }; + B10B1100B10B1100B10B1201 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B10B1100B10B1100B10B1202 /* PrivacyInfo.xcprivacy */; }; B10B1100B10B1100B10B1101 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B10B1100B10B1100B10B1102 /* InfoPlist.strings */; }; /* End PBXBuildFile section */ @@ -31,6 +32,7 @@ 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; 9582B6822FE993A50072D4E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; }; + B10B1100B10B1100B10B1202 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; B10B1100B10B1100B10B1103 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -74,6 +76,7 @@ 504EC3101FED79650016851F /* LaunchScreen.storyboard */, 504EC3131FED79650016851F /* Info.plist */, B10B1100B10B1100B10B1102 /* InfoPlist.strings */, + B10B1100B10B1100B10B1202 /* PrivacyInfo.xcprivacy */, 2FAD9762203C412B000D30F8 /* config.xml */, 50B271D01FEDC1A000F3C39B /* public */, ); @@ -153,6 +156,7 @@ 504EC30D1FED79650016851F /* Main.storyboard in Resources */, 2FAD9763203C412B000D30F8 /* config.xml in Resources */, B10B1100B10B1100B10B1101 /* InfoPlist.strings in Resources */, + B10B1100B10B1100B10B1201 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/app/ios/App/App/Info.plist b/app/ios/App/App/Info.plist index 451a0eb..7f2d41f 100644 --- a/app/ios/App/App/Info.plist +++ b/app/ios/App/App/Info.plist @@ -6,6 +6,11 @@ $(CAPACITOR_DEBUG) CFBundleDevelopmentRegion en + CFBundleLocalizations + + en + de + CFBundleDisplayName Blobling CFBundleExecutable diff --git a/app/ios/App/App/PrivacyInfo.xcprivacy b/app/ios/App/App/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..a068a59 --- /dev/null +++ b/app/ios/App/App/PrivacyInfo.xcprivacy @@ -0,0 +1,42 @@ + + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCoarseLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + + diff --git a/app/ios/App/CapApp-SPM/Package.swift b/app/ios/App/CapApp-SPM/Package.swift index 5974f4b..571cbd1 100644 --- a/app/ios/App/CapApp-SPM/Package.swift +++ b/app/ios/App/CapApp-SPM/Package.swift @@ -13,6 +13,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.5.3"), .package(name: "CapacitorFilesystem", path: "../../../../node_modules/@capacitor/filesystem"), + .package(name: "CapacitorGeolocation", path: "../../../../node_modules/@capacitor/geolocation"), .package(name: "CapacitorHaptics", path: "../../../../node_modules/@capacitor/haptics"), .package(name: "CapacitorLocalNotifications", path: "../../../../node_modules/@capacitor/local-notifications") ], @@ -23,6 +24,7 @@ let package = Package( .product(name: "Capacitor", package: "capacitor-swift-pm"), .product(name: "Cordova", package: "capacitor-swift-pm"), .product(name: "CapacitorFilesystem", package: "CapacitorFilesystem"), + .product(name: "CapacitorGeolocation", package: "CapacitorGeolocation"), .product(name: "CapacitorHaptics", package: "CapacitorHaptics"), .product(name: "CapacitorLocalNotifications", package: "CapacitorLocalNotifications") ] diff --git a/app/locales/de.json b/app/locales/de.json index 4ca2a71..beb5804 100644 --- a/app/locales/de.json +++ b/app/locales/de.json @@ -629,6 +629,10 @@ "location_set": "Wetter für {latitude}, {longitude}.", "locate_failed": "Dein Ort ließ sich nicht herausfinden.", "connect_note": "Blobling gibt es auch als LED-Display zum Selberbauen.", + "privacy": "Datenschutz", + "support": "Hilfe", + "licenses": "Lizenzen", + "version": "Version {version}", "connect": "Mit dem Display verbinden", "connect_prompt": "Adresse des Displays, z. B. raspberrypi.local oder blobling.local", "connect_ok": "Verbinden", diff --git a/app/locales/en.json b/app/locales/en.json index 2365b76..84c1832 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -629,6 +629,10 @@ "location_set": "Weather for {latitude}, {longitude}.", "locate_failed": "Couldn't find out where you are.", "connect_note": "Blobling also comes as an LED display to build yourself.", + "privacy": "Privacy policy", + "support": "Help", + "licenses": "Licenses", + "version": "Version {version}", "connect": "Connect to the display", "connect_prompt": "Address of the display, e.g. raspberrypi.local or blobling.local", "connect_ok": "Connect", diff --git a/app/pet.html b/app/pet.html index 618eb6c..f63202c 100644 --- a/app/pet.html +++ b/app/pet.html @@ -496,6 +496,7 @@

+ @@ -2211,6 +2212,16 @@

$("build-link").hidden = $("connect").hidden = !!app.connectedTo; $("disconnect").hidden = $("offline-leave").hidden = !app.connectedTo; } +// The app from the stores links its privacy policy and help (docs/ on GitHub Pages), as the stores ask +if (app.native) { + const docs = "https://mlampir.github.io/blobling/"; + const de = LANG.startsWith("de") ? ".de" : ""; + $("privacy-link").href = `${docs}privacy${de}`; + $("support-link").href = `${docs}support${de}`; + $("licenses-link").href = `${docs}licenses`; + if (app.version) $("version").textContent = ` · ${t("page.app.version", { version: app.version })}`; + $("legal").hidden = false; +} // The Pi's own page shows the display part only for its update if (!app.native) $("display-note").hidden = $("connect").hidden = $("disconnect").hidden = true; diff --git a/app/src/main.ts b/app/src/main.ts index 2caebf3..8669642 100644 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -80,6 +80,8 @@ const app = { mode, /** The page is the app from the stores. */ native, + /** The app's version in a build for the stores, "" otherwise. */ + version: __APP_VERSION__, /** The Pi's address, if the app is connected to one. */ connectedTo, /** All texts in the page's language, {key: text}. */ diff --git a/app/src/native.ts b/app/src/native.ts index 260409a..41278e0 100644 --- a/app/src/native.ts +++ b/app/src/native.ts @@ -4,22 +4,20 @@ */ import { Capacitor } from "@capacitor/core"; +import { Geolocation } from "@capacitor/geolocation"; /** Whether the page runs inside the app from the stores. */ export const native = Capacitor.isNativePlatform(); -/** Where the phone is, as [latitude, longitude]. Throws if it can't tell (or may not). */ -export function locate(): Promise<[number, number]> { - return new Promise((resolve, reject) => { - if (!navigator.geolocation) { - reject(new Error("no geolocation")); - return; - } - // coarse is enough for the weather, and quicker - navigator.geolocation.getCurrentPosition( - ({ coords }) => resolve([coords.latitude, coords.longitude]), - (error) => reject(new Error(error.message)), - { enableHighAccuracy: false, timeout: 15_000, maximumAge: 3600_000 }, - ); - }); +/** + * Where the phone is, as [latitude, longitude]. Throws if it can't tell (or may not). + * + * In the app through Capacitor's Geolocation, not the WebView's navigator.geolocation: on iOS the + * WebView would ask a second time on its own ("localhost would like to use your location"), and + * on Android it would also ask for the precise location. Approximate is enough for the weather. + */ +export async function locate(): Promise<[number, number]> { + const { coords } = await Geolocation.getCurrentPosition( + { enableHighAccuracy: false, timeout: 15_000, maximumAge: 3600_000 }); + return [coords.latitude, coords.longitude]; } diff --git a/app/src/virtual.d.ts b/app/src/virtual.d.ts index b8486c5..61e92b4 100644 --- a/app/src/virtual.d.ts +++ b/app/src/virtual.d.ts @@ -17,3 +17,6 @@ declare module "blobling:sounds" { const sounds: Record; export default sounds; } + +/** The app's version ("1.2.0") in a build for the stores, "" otherwise (tools/build.mjs). */ +declare const __APP_VERSION__: string; diff --git a/app/test/languages.test.ts b/app/test/languages.test.ts new file mode 100644 index 0000000..89e441a --- /dev/null +++ b/app/test/languages.test.ts @@ -0,0 +1,22 @@ +// The phone's settings offer the app's languages from two lists of their own; both have to name +// every language file in app/locales/. + +import assert from "node:assert/strict"; +import { readFileSync, readdirSync } from "node:fs"; +import { test } from "node:test"; + +const files = readdirSync("app/locales").filter((name) => name.endsWith(".json")) + .map((name) => name.slice(0, -".json".length)).sort(); + +test("Android's locales_config.xml names every language file", () => { + const config = readFileSync("app/android/app/src/main/res/xml/locales_config.xml", "utf-8"); + const names = [...config.matchAll(/ match[1]).sort(); + assert.deepEqual(names, files); +}); + +test("iOS's CFBundleLocalizations names every language file", () => { + const plist = readFileSync("app/ios/App/App/Info.plist", "utf-8"); + const list = plist.match(/CFBundleLocalizations<\/key>\s*([\s\S]*?)<\/array>/)?.[1] ?? ""; + const names = [...list.matchAll(/([\w-]+)<\/string>/g)].map((match) => match[1]).sort(); + assert.deepEqual(names, files); +}); diff --git a/docs/licenses.md b/docs/licenses.md new file mode 100644 index 0000000..fa5664e --- /dev/null +++ b/docs/licenses.md @@ -0,0 +1,63 @@ +# Blobling – Licenses + +Blobling is free software under the GNU General Public License, version 2 or +(at your option) any later version. Its source code, the sprites and the +sounds are at ; the sprites are under +CC BY-NC 4.0 (see `app/pet/assets/LICENSE.txt` there). + +The app from the stores is built with the following open-source parts, under +their own licenses. + +## Weather data + +Weather data by [Open-Meteo.com](https://open-meteo.com/), under +[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). + +## MIT License + +- **Capacitor** (`@capacitor/core`, `@capacitor/android`, `@capacitor/ios`): + Copyright (c) 2017-present Drifty Co. +- **Capacitor plugins** Filesystem, Geolocation, Haptics and Local + Notifications: Copyright (c) 2020-present Ionic +- **ion-android-filesystem, ion-ios-filesystem, ion-android-geolocation, + ion-ios-geolocation**: Copyright (c) OutSystems / Ionic + +``` +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## Apache License 2.0 + +- **Apache Cordova** (Android framework, part of Capacitor's Cordova + support): Copyright The Apache Software Foundation +- **AndroidX** libraries (AppCompat, Core, Activity, Fragment, + CoordinatorLayout, WebKit, SplashScreen): Copyright The Android Open Source + Project +- **Kotlin** standard library and **kotlinx.coroutines**: Copyright JetBrains + s.r.o. and Kotlin Programming Language contributors +- **Gson**: Copyright Google LLC + +Licensed under the Apache License, Version 2.0: +. Distributed on an "AS IS" +basis, without warranties or conditions of any kind. + +## Google Play services (Android) + +The Android app uses Google Play services for the location, under the +[Android Software Development Kit License](https://developer.android.com/studio/terms). diff --git a/docs/translating.md b/docs/translating.md index b515ecf..085a679 100644 --- a/docs/translating.md +++ b/docs/translating.md @@ -20,7 +20,10 @@ language = de Times and dates on the pet page follow the phone's own settings, not this option. The app on the phone takes the phone's language, if there is a file -for it; the pet page the Pi serves comes in the Pi's language. +for it, or the language chosen for the app in the phone's settings (Android +13 and later: *Settings → Apps → Blobling → Language*; iOS: *Settings → +Blobling → Language*, shown once the phone has more than one language in its +list). The pet page the Pi serves comes in the Pi's language. ## Adding a language @@ -32,7 +35,10 @@ for it; the pet page the Pi serves comes in the Pi's language. 3. Restart the app (`sudo systemctl restart led-matrix`), then choose the language on the settings page. -No code changes are needed. A few things to know while translating: +For the app from the stores, also add the code to the two lists the phones' +settings take the app's languages from: `app/android/app/src/main/res/xml/locales_config.xml` +and `CFBundleLocalizations` in `app/ios/App/App/Info.plist` (a test checks +both). No other code changes are needed. A few things to know while translating: - **Placeholders** in curly braces, such as `{name}` or `{time}`, are filled in by the app. Keep them, but move them wherever your language needs them. diff --git a/package-lock.json b/package-lock.json index cec476a..db8a5d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "dependencies": { "@capacitor/core": "8.5.3", "@capacitor/filesystem": "8.1.4", + "@capacitor/geolocation": "8.2.4", "@capacitor/haptics": "8.0.2", "@capacitor/local-notifications": "8.3.1" }, @@ -90,6 +91,18 @@ "@capacitor/core": ">=8.0.0" } }, + "node_modules/@capacitor/geolocation": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@capacitor/geolocation/-/geolocation-8.2.4.tgz", + "integrity": "sha512-FDcUiPQc2AXSTAV61QSDPXml11guKBx5HPEkdWvUqw7kMqrc2Ucp0bsWngeLyRs/01a5aWBI0hEuvetuF3PsrQ==", + "license": "MIT", + "dependencies": { + "@capacitor/synapse": "^1.0.4" + }, + "peerDependencies": { + "@capacitor/core": ">=8.0.0" + } + }, "node_modules/@capacitor/haptics": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-8.0.2.tgz", diff --git a/package.json b/package.json index b0848f4..6a5ac04 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "dependencies": { "@capacitor/core": "8.5.3", "@capacitor/filesystem": "8.1.4", + "@capacitor/geolocation": "8.2.4", "@capacitor/haptics": "8.0.2", "@capacitor/local-notifications": "8.3.1" } diff --git a/store/listing.en.md b/store/listing.en.md index 4896fd5..43f8653 100644 --- a/store/listing.en.md +++ b/store/listing.en.md @@ -21,6 +21,30 @@ Meet Blobling, a little pixel pet that lives on your phone. Hatch it, look after virtual pet,pixel,cute,slime,egg,hatch,raise,care,adventure,retro,cozy,chiptune,led,offline +## App Store: notes for App Review + +(App Store Connect, the version's *App Review Information → Notes*; the same +text fits Google Play's *App access*, with "All functionality is available +without special access".) Reviewers write in English, so this exists in +English only. + +Blobling needs no account, no login and no hardware: the whole game runs on +the phone. A new pet starts as an egg that hatches within about an hour +(stroking it keeps it warm and makes it sooner). Then drag food from the +tray onto the pet and stroke it. The "More" tab has the name, a new egg, +sound and the optional weather. + +The section "Own LED display" in "More" is optional. It connects the app to +a self-built LED panel (a Raspberry Pi on the home network, instructions at +https://github.com/mLampir/blobling). That is why the app asks for local +network access and talks plain HTTP to private addresses only. Without such +a display, the search finds nothing, asks for an address, and the pet stays +on the phone. + +Location is optional and only used for the weather in the pet's world: the +app rounds it to about 1 km and sends it to Open-Meteo. Notifications are +local only (the pet asks for you when it needs you); there is no server. + ## Description (Google Play full description and App Store description) Blobling is a little pixel pet that lives on your phone. It hatches from an egg, wants your attention a few times a day, and grows up depending on how you raise it. diff --git a/tools/build.mjs b/tools/build.mjs index bf276d1..4c083f6 100644 --- a/tools/build.mjs +++ b/tools/build.mjs @@ -66,7 +66,9 @@ await build({ ...shared, entryPoints: ["pi/src/main.ts"], platform: "node", targ outfile: "dist/pi/server.mjs" }); await build({ ...shared, entryPoints: ["app/src/main.ts"], platform: "browser", format: "iife", minify: true, sourcemap: false, // what the phones of the last few years run (Android WebView, iOS Safari 15) - target: ["es2020", "chrome90", "safari15"], plugins: [appFiles], outfile: "dist/app/app.js" }); + target: ["es2020", "chrome90", "safari15"], plugins: [appFiles], outfile: "dist/app/app.js", + // the version from the release tag when Codemagic builds the app for the stores (codemagic.yaml), else "" + define: { __APP_VERSION__: JSON.stringify(process.env.VERSION ?? "") } }); mkdirSync("dist/app", { recursive: true }); copyFileSync("app/pet.html", "dist/app/index.html"); await build({ ...core, platform: "node", target: "node22", outfile: "dist/core.node.mjs" });