diff --git a/PRIVACY.md b/PRIVACY.md index e89a480b..508ca0a0 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ # Loopky Privacy Policy -**Last updated:** 23 August 2026 +**Last updated:** 30 August 2026 Loopky is a flashcards app built on [Pubky](https://pubky.org). This policy describes what happens to your information when you use it. @@ -107,7 +107,7 @@ has its own operator and its own privacy practices. | **Pubky Nexus indexer** (`nexus.pubky.app`) | Search, discovery, trending topics, profile pictures | Your search terms, the public keys and decks you look up, your IP address | | **Homegate** (`homegate.pubky.app`) | Signing up for a new homeserver account only | Your phone number if you verify by SMS, or Lightning payment details if you pay. Your phone number is handled by Homegate and is never stored by Loopky | | **Unsplash** (`api.unsplash.com`) | Only when you search for a picture from the web | Your search terms and your IP address, under [Unsplash's privacy policy](https://unsplash.com/privacy) | -| **Google Play** | Only if you tap to install Pubky Ring from the backup screen | Standard Play Store request | +| **Google Play** or the **App Store** | Only if you tap to install Pubky Ring from the backup screen | A standard store request, to whichever store your device uses | Loopky does not contact any of these to report on you. It contacts them to do the thing you asked for. @@ -125,16 +125,24 @@ The **Speak** practice mode records audio so you can practise pronunciation. - The permission is requested the first time you use Speak, and Speak is optional. Declining it leaves the rest of the app working normally. -Depending on your device settings, your device's speech recognition may itself run in the cloud -rather than on-device. That processing is governed by your device manufacturer's or Google's -privacy policy, not this one. +Depending on your device and its settings, your device's speech recognition may itself run in the +cloud rather than on-device — on iOS through Apple's speech recognition, on Android through +Google's. Loopky does not force either mode, so which one you get is your platform's decision. That +processing is governed by Apple's or Google's privacy policy, not this one. --- -## 7. Photos and images +## 7. Camera, photos and images -Choosing a picture for a card uses the Android system photo picker, which hands Loopky only the one -image you select. Loopky requests no storage or gallery permission. +**The camera is used for one thing: reading a QR code that holds someone's pubky**, so you can add +them without typing a long key by hand. Loopky reads only the text decoded from the code. No photo +or video is captured, saved or uploaded, and the camera runs only while that scanner is open. The +permission is requested the first time you open it, and scanning is optional — you can always type +or paste a pubky instead. + +Choosing a picture for a card uses the system photo picker — `PhotosPicker` on iOS, the Android +photo picker on Android. Both run outside Loopky and hand it only the one image you select, so +Loopky requests no gallery or storage permission on either platform. Images you attach to a published deck are uploaded to your homeserver and are public along with the deck. diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig index 1ea244ba..275cb449 100644 --- a/iosApp/Configuration/Config.xcconfig +++ b/iosApp/Configuration/Config.xcconfig @@ -1,7 +1,29 @@ +// Your Apple Developer Team ID, from developer.apple.com → Membership. +// Ten characters, e.g. A1B2C3D4E5. Needed to sign a device build or archive; +// a simulator build works without it. TEAM_ID= +// The app's permanent identity on the App Store. +// +// This is deliberately NOT derived from TEAM_ID. The Kotlin Multiplatform template +// ships it as `...Loopky$(TEAM_ID)`, which keeps two developers' dev builds from +// colliding but makes the bundle identifier change the moment TEAM_ID is filled in — +// silently breaking the match with the App Store Connect record and every provisioning +// profile. It is fixed here instead. +// +// Change it only BEFORE the app record exists in App Store Connect. After that it is +// permanent: a new identifier is a new app, with no path to move testers or reviews. +// +// Matches Android's applicationId exactly. It also makes the identifiers already in +// Info.plist real children of it rather than merely looking like them: the two +// BGTaskScheduler ids, the `...loopky.login` URL name, and the `.apkg` / `.pkarr` UTIs +// were all written as `com.github.jvsena42.loopky.*` while the bundle was +// `...loopky.Loopky`, so none of them actually nested under it. +PRODUCT_BUNDLE_IDENTIFIER=com.github.jvsena42.loopky + PRODUCT_NAME=Loopky -PRODUCT_BUNDLE_IDENTIFIER=com.github.jvsena42.loopky.Loopky$(TEAM_ID) +// MARKETING_VERSION is what testers see. CURRENT_PROJECT_VERSION must be unique for +// every upload to App Store Connect — bump it per upload, not per release. CURRENT_PROJECT_VERSION=13 -MARKETING_VERSION=0.6.1 \ No newline at end of file +MARKETING_VERSION=0.6.1 diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index ce8b66df..0be7ecc6 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -2,8 +2,28 @@ + BGTaskSchedulerPermittedIdentifiers + + com.github.jvsena42.loopky.media-rehost + com.github.jvsena42.loopky.deck-compaction + CADisableMinimumFrameDurationOnPhone + CFBundleDocumentTypes + + + CFBundleTypeName + Anki deck + LSHandlerRank + Alternate + LSItemContentTypes + + com.github.jvsena42.loopky.apkg + public.plain-text + public.comma-separated-values-text + + + CFBundleURLTypes @@ -15,60 +35,38 @@ - - - + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + pubkyring + pubkyauth + + LSSupportsOpeningDocumentsInPlace + NSCameraUsageDescription Loopky uses the camera to read a QR code holding someone's pubky, so you can find them without typing it. NSMicrophoneUsageDescription Loopky listens while you practise saying a card out loud, and stops as soon as you have answered. NSSpeechRecognitionUsageDescription Loopky turns what you say into text to check your pronunciation against the card. Nothing is stored. - CFBundleDocumentTypes + UIBackgroundModes - - CFBundleTypeName - Anki deck - LSHandlerRank - Alternate - LSItemContentTypes - - com.github.jvsena42.loopky.apkg - public.plain-text - public.comma-separated-values-text - - + processing - + UIFileSharingEnabled + UTExportedTypeDeclarations - UTTypeIdentifier - com.github.jvsena42.loopky.pkarr - UTTypeDescription - Pubky recovery file UTTypeConformsTo public.data + UTTypeDescription + Pubky recovery file + UTTypeIdentifier + com.github.jvsena42.loopky.pkarr UTTypeTagSpecification public.filename-extension @@ -81,14 +79,14 @@ UTImportedTypeDeclarations - UTTypeIdentifier - com.github.jvsena42.loopky.apkg - UTTypeDescription - Anki deck UTTypeConformsTo public.zip-archive + UTTypeDescription + Anki deck + UTTypeIdentifier + com.github.jvsena42.loopky.apkg UTTypeTagSpecification public.filename-extension @@ -98,25 +96,5 @@ - UIFileSharingEnabled - - LSSupportsOpeningDocumentsInPlace - - BGTaskSchedulerPermittedIdentifiers - - com.github.jvsena42.loopky.media-rehost - com.github.jvsena42.loopky.deck-compaction - - LSApplicationQueriesSchemes - - pubkyring - - pubkyauth - - UIBackgroundModes - - processing - diff --git a/iosApp/iosApp/PrivacyInfo.xcprivacy b/iosApp/iosApp/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..4ace3a4f --- /dev/null +++ b/iosApp/iosApp/PrivacyInfo.xcprivacy @@ -0,0 +1,57 @@ + + + + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + + NSPrivacyCollectedDataTypes + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + +