From 54311bedd09e5305440c47f5215cc47f6278b49f Mon Sep 17 00:00:00 2001 From: Mohammad Abdul Sahil <127765312+abdulsaheel@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:21:18 +0530 Subject: [PATCH] Warn on iOS that deleting the app unpairs bands and sensors for every app (#538) AccessorySetupKit pairings are removed by iOS, bond included and for all apps, when OpenStrap is uninstalled. There is no hook to prevent it, so the sensor pairing screen, both forget dialogs and the reset dialog now say so on iOS only. New string translated for every shipped locale. --- lib/l10n/app_de.arb | 1 + lib/l10n/app_en.arb | 4 ++++ lib/l10n/app_es.arb | 1 + lib/l10n/app_fr.arb | 1 + lib/l10n/app_hi.arb | 1 + lib/l10n/app_ru.arb | 1 + lib/l10n/app_zh.arb | 1 + lib/l10n/display_text.dart | 9 +++++++++ lib/ui2/profile/devices.dart | 8 ++++---- lib/ui2/profile/pair_sensor.dart | 4 ++-- lib/ui2/profile/settings.dart | 4 ++-- test/pair_sensor_test.dart | 17 +++++++++++++++++ 12 files changed, 44 insertions(+), 8 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 744868aaf..16c4ccabe 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -281,6 +281,7 @@ "pairSensorUsedDuringWorkouts": "Wird beim Training verwendet", "pairSensorForgetThisSensor": "Diesen Sensor entfernen", "pairSensorForgetThisSensorSub": "Entfernt die Quelle. Bereits erfasste Messwerte bleiben erhalten.", + "iosUninstallUnpairsWarning": "Unter iOS hebt das Löschen von OpenStrap auch die Kopplung aller hier gekoppelten Bänder und Sensoren mit dem Telefon auf, sodass auch andere Apps, einschließlich der App des Herstellers, sie verlieren.", "pairSensorPairing": "Kopplung läuft…", "pairSensorScanDidNotRun": "Die Suche konnte nicht gestartet werden: {error}", "pairSensorCouldNotPair": "Gerät konnte nicht gekoppelt werden: {error}", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7cdb11fde..baede8163 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1180,6 +1180,10 @@ "@pairSensorForgetThisSensorSub": { "description": "Row sub: forget sensor." }, + "iosUninstallUnpairsWarning": "On iOS, deleting OpenStrap also unpairs every band and sensor paired here from the phone, so other apps, including the maker's own, lose them too.", + "@iosUninstallUnpairsWarning": { + "description": "iOS only: AccessorySetupKit pairings are removed by iOS for all apps when OpenStrap is deleted." + }, "pairSensorPairing": "Pairing…", "@pairSensorPairing": { "description": "Row sub: pairing in progress." diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index cb9af1212..5148594b9 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -245,6 +245,7 @@ "pairSensorUsedDuringWorkouts": "Se usa durante los entrenamientos", "pairSensorForgetThisSensor": "Olvidar este sensor", "pairSensorForgetThisSensorSub": "Elimina la fuente. Las lecturas que ya tomó se mantienen.", + "iosUninstallUnpairsWarning": "En iOS, eliminar OpenStrap también desvincula del teléfono todas las pulseras y sensores emparejados aquí, así que otras apps, incluida la del fabricante, también los pierden.", "pairSensorPairing": "Emparejando…", "pairSensorScanDidNotRun": "La búsqueda no se ejecutó: {error}", "pairSensorCouldNotPair": "No se pudo emparejar ese dispositivo: {error}", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 6b2495e1e..4e83afb21 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -245,6 +245,7 @@ "pairSensorUsedDuringWorkouts": "Utilisé pendant les entraînements", "pairSensorForgetThisSensor": "Oublier ce capteur", "pairSensorForgetThisSensorSub": "Supprime la source. Les relevés déjà effectués restent.", + "iosUninstallUnpairsWarning": "Sur iOS, supprimer OpenStrap désappaire aussi du téléphone chaque bracelet et capteur appairé ici, si bien que les autres apps, y compris celle du fabricant, les perdent aussi.", "pairSensorPairing": "Jumelage…", "pairSensorScanDidNotRun": "La recherche ne s’est pas exécutée : {error}", "pairSensorCouldNotPair": "Impossible de jumeler cet appareil : {error}", diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index b852c0b80..1d1a08913 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -245,6 +245,7 @@ "pairSensorUsedDuringWorkouts": "वर्कआउट के दौरान उपयोग होता है", "pairSensorForgetThisSensor": "इस सेंसर को भूल जाएं", "pairSensorForgetThisSensorSub": "स्रोत हटाता है। पहले से ली गई रीडिंग बनी रहती हैं।", + "iosUninstallUnpairsWarning": "iOS पर OpenStrap हटाने से यहाँ पेयर किए गए हर बैंड और सेंसर की फ़ोन से पेयरिंग भी हट जाती है, इसलिए निर्माता के अपने ऐप सहित दूसरे ऐप भी उन्हें खो देते हैं।", "pairSensorPairing": "पेयर हो रहा है…", "pairSensorScanDidNotRun": "स्कैन नहीं चला: {error}", "pairSensorCouldNotPair": "उस डिवाइस को पेयर नहीं किया जा सका: {error}", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 94e5f2eb1..561c4cc21 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -402,6 +402,7 @@ "pairSensorUsedDuringWorkouts": "Используется во время тренировок", "pairSensorForgetThisSensor": "Удалить этот датчик", "pairSensorForgetThisSensorSub": "Удаляет источник. Уже записанные данные сохраняются.", + "iosUninstallUnpairsWarning": "На iOS удаление OpenStrap также снимает с телефона сопряжение всех браслетов и датчиков, сопряжённых здесь, поэтому другие приложения, включая приложение производителя, тоже их теряют.", "pairSensorPairing": "Сопряжение…", "pairSensorScanDidNotRun": "Не удалось выполнить поиск: {error}", "@pairSensorScanDidNotRun": { diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 46c33f878..7cf590d8c 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -245,6 +245,7 @@ "pairSensorUsedDuringWorkouts": "在锻炼期间使用", "pairSensorForgetThisSensor": "忘记此传感器", "pairSensorForgetThisSensorSub": "移除数据源。已获取的读数会保留。", + "iosUninstallUnpairsWarning": "在 iOS 上,删除 OpenStrap 也会解除此处配对的所有手环和传感器与手机的配对,因此其他应用(包括厂商自己的应用)也会失去它们。", "pairSensorPairing": "配对中…", "pairSensorScanDidNotRun": "扫描未运行:{error}", "pairSensorCouldNotPair": "无法配对该设备:{error}", diff --git a/lib/l10n/display_text.dart b/lib/l10n/display_text.dart index 16cd20c69..ea284a300 100644 --- a/lib/l10n/display_text.dart +++ b/lib/l10n/display_text.dart @@ -1,10 +1,19 @@ // Presentation-only vocabulary. Never pass user-entered text or storage keys. +import 'package:flutter/foundation.dart' show TargetPlatform, defaultTargetPlatform; import 'package:flutter/widgets.dart'; import 'app_localizations.dart'; String uiText(BuildContext context, String text) => localizedText(AppLocalizations.of(context), text); +/// iOS deletes AccessorySetupKit pairings, bond included and for every app, +/// when OpenStrap is uninstalled, and there is no hook to stop it (#538). +/// Appended to pairing and unpair/reset copy so nobody learns it afterwards. +String withIosUninstallWarning(BuildContext context, String body) => + defaultTargetPlatform == TargetPlatform.iOS + ? '$body\n\n${AppLocalizations.of(context)?.iosUninstallUnpairsWarning ?? "On iOS, deleting OpenStrap also unpairs every band and sensor paired here from the phone, so other apps, including the maker's own, lose them too."}' + : body; + String journalTagText(BuildContext context, String tag) => localizedJournalTag(AppLocalizations.of(context), tag); diff --git a/lib/ui2/profile/devices.dart b/lib/ui2/profile/devices.dart index 900f5ed3a..273ed4d26 100644 --- a/lib/ui2/profile/devices.dart +++ b/lib/ui2/profile/devices.dart @@ -2518,10 +2518,10 @@ Future _confirmForgetSensor(BuildContext c, HealthSource s) async { l?.devicesForgetSensor(s.displayName(c)) ?? 'Forget ${s.name}?', ), content: Text( - l?.devicesForgetSensorBody ?? + withIosUninstallWarning(c, l?.devicesForgetSensorBody ?? 'It stops being used during workouts and has to be paired again. ' 'Everything already banked on this phone is kept — this removes the ' - 'source, not the data.', + 'source, not the data.'), ), actions: [ TextButton( @@ -2627,10 +2627,10 @@ Future _confirmForget(BuildContext c, AppState app, String name) async { builder: (d) => AlertDialog( title: Text(l?.devicesForgetBand(name) ?? 'Forget $name?'), content: Text( - l?.devicesForgetBandBody ?? + withIosUninstallWarning(c, l?.devicesForgetBandBody ?? 'The band stops syncing and has to be paired again to measure ' 'anything. Everything already banked on this phone is kept — this ' - 'removes the source, not the data.', + 'removes the source, not the data.'), ), actions: [ TextButton( diff --git a/lib/ui2/profile/pair_sensor.dart b/lib/ui2/profile/pair_sensor.dart index 43de2bb1e..527b19586 100644 --- a/lib/ui2/profile/pair_sensor.dart +++ b/lib/ui2/profile/pair_sensor.dart @@ -384,12 +384,12 @@ class PairSensorView extends StatelessWidget { : (l?.pairSensorPairAnother ?? 'Pair another'), Surface( child: Text( - l?.pairSensorExplainer ?? + withIosUninstallWarning(c, l?.pairSensorExplainer ?? 'A sensor is used only while a workout is running, and ' 'only for heart rate and beat timing. It does not ' 'replace your band, it is never used overnight, and ' 'nothing it records feeds a score yet — its readings are ' - 'stored and shown, and that is all.', + 'stored and shown, and that is all.'), style: F.cap.copyWith(color: p.ink3, height: 1.5), ), ), diff --git a/lib/ui2/profile/settings.dart b/lib/ui2/profile/settings.dart index e06d9df15..2c6ae138f 100644 --- a/lib/ui2/profile/settings.dart +++ b/lib/ui2/profile/settings.dart @@ -452,7 +452,7 @@ Future _confirmReset(BuildContext c, AppState app) async { // install id, the stored API key and the home-screen widget standing. // It now removes all of that, so it can say so. content: Text( - l?.settingsResetBody ?? + withIosUninstallWarning(c, l?.settingsResetBody ?? 'This permanently deletes this app\'s data on this device:\n\n' '· every measured day, sleep, workout and route\n' '· every lab result, meal, medication dose, habit, breathing session ' @@ -463,7 +463,7 @@ Future _confirmReset(BuildContext c, AppState app) async { 'Exported copies and backups in previous or unavailable folders ' 'may remain. Delete those separately.\n\n' 'The band is unpaired, and it cannot re-send history it has already ' - 'handed over. Export from Your data first if you want a copy.', + 'handed over. Export from Your data first if you want a copy.'), ), actions: [ TextButton( diff --git a/test/pair_sensor_test.dart b/test/pair_sensor_test.dart index 37c02b3da..73ee8d961 100644 --- a/test/pair_sensor_test.dart +++ b/test/pair_sensor_test.dart @@ -18,6 +18,7 @@ // The scan itself is not here and cannot be: `flutter_blue_plus` has no // simulator path, so a scan test would only prove the fake. +import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart' show BluetoothDevice; import 'package:flutter_test/flutter_test.dart'; @@ -206,6 +207,22 @@ void main() { expect(find.text('Search anyway'), findsOneWidget); }); + // #538: iOS removes AccessorySetupKit pairings for every app when + // OpenStrap is deleted. Say so before pairing, and only on iOS. + for (final platform in [TargetPlatform.iOS, TargetPlatform.android]) { + testWidgets('the uninstall-unpairs warning on ${platform.name}', + (t) async { + debugDefaultTargetPlatformOverride = platform; + await _pump(t, + const PairSensorView(entryLabel: 'Bluetooth heart rate sensor')); + expect( + find.textContaining('deleting OpenStrap also unpairs'), + platform == TargetPlatform.iOS ? findsOneWidget : findsNothing, + ); + debugDefaultTargetPlatformOverride = null; + }); + } + // Order is the scan's (strongest signal first) and is applied in // `HrsLink._ranked`, which cannot be reached without a radio — the view // draws what it is handed, which is what this pins.