diff --git a/Strand/Resources/Localizable.xcstrings b/Strand/Resources/Localizable.xcstrings index 1a6758dc5d..b9211d0135 100644 --- a/Strand/Resources/Localizable.xcstrings +++ b/Strand/Resources/Localizable.xcstrings @@ -161701,6 +161701,64 @@ } } }, + "This export contains raw biometric frames, strap console text, and the connection log. Identifiers are redacted automatically. Share it only if you are comfortable with that.": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Export enthält biometrische Rohdaten, Konsolentext des Armbands und das Verbindungsprotokoll. Kennungen werden automatisch geschwärzt. Teile ihn nur, wenn du damit einverstanden bist." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esta exportación contiene tramas biométricas sin procesar, texto de la consola de la pulsera y el registro de conexión. Los identificadores se ocultan automáticamente. Compártela solo si te sientes cómodo haciéndolo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cette exportation contient des trames biométriques brutes, le texte de la console du bracelet et le journal de connexion. Les identifiants sont automatiquement masqués. Ne la partagez que si cela vous convient." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa esportazione contiene frame biometrici grezzi, testo della console del bracciale e il registro di connessione. Gli identificatori vengono oscurati automaticamente. Condividila solo se ti senti a tuo agio." + } + }, + "pt-PT": { + "stringUnit": { + "state": "translated", + "value": "Esta exportação contém tramas biométricas em bruto, texto da consola da bracelete e o registo de ligação. Os identificadores são ocultados automaticamente. Partilha-a apenas se estiveres confortável com isso." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Этот экспорт содержит необработанные биометрические кадры, текст консоли браслета и журнал подключения. Идентификаторы автоматически скрываются. Делитесь им, только если вам это комфортно." + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "此导出包含原始生物识别数据帧、手环控制台文本和连接日志。标识符会自动隐去。请仅在你对此感到放心时分享。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "此匯出包含原始生物辨識資料幀、手環主控台文字和連線記錄。識別碼會自動隱去。請僅在你對此感到放心時分享。" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ten eksport zawiera surowe ramki danych biometrycznych, tekst konsoli opaski i dziennik połączenia. Identyfikatory są automatycznie ukrywane. Udostępnij go tylko, jeśli czujesz się z tym komfortowo." + } + } + } + }, "This is exactly what your report will contain. Nothing leaves %@ until you tap Share.": { "localizations": { "de": { @@ -212449,4 +212507,4 @@ } } }, "version": "1.0" -} \ No newline at end of file +} diff --git a/Strand/Screens/SettingsView.swift b/Strand/Screens/SettingsView.swift index a17a8642b9..892b76aa0d 100644 --- a/Strand/Screens/SettingsView.swift +++ b/Strand/Screens/SettingsView.swift @@ -277,6 +277,7 @@ struct SettingsView: View { /// sheets (see the `present(activityItems:)` #455 comment). Same disable-while-busy guard as /// `rawCsvBusy` above. @State private var rawAndLogBusy = false + @State private var showRawExportConfirm = false /// Passive WHOOP 5/MG optical experiment: the picker writes local timestamp markers into the /// durable deep-buffer JSONL. It never calls a BLE write path. @@ -383,6 +384,12 @@ struct SettingsView: View { } message: { Text("This restarts the roughly 4-night build-up for Charge and your HRV baseline. Your history stays. Use it if a bad first week, like wearing it while sick, set your baseline off.") } + .confirmationDialog("Review before sharing", isPresented: $showRawExportConfirm, titleVisibility: .visible) { + Button("Export") { exportRawAndLog() } + Button("Cancel", role: .cancel) { } + } message: { + Text("This export contains raw biometric frames, strap console text, and the connection log. Identifiers are redacted automatically. Share it only if you are comfortable with that.") + } // #174: the switch going OFF is the moment to offer the undo. Declining leaves the flags set and // says so — which is still an improvement on the old behaviour, where the same tap silently left // them set with no indication either way. @@ -2303,7 +2310,7 @@ struct SettingsView: View { // and the strap log together (timestamped, same minute) so a protocol-mapping issue // arrives with the frames AND the context that produced them. Button { - exportRawAndLog() + showRawExportConfirm = true } label: { if rawAndLogBusy { HStack(spacing: NoopMetrics.space1 + 2) { @@ -2545,10 +2552,15 @@ struct SettingsView: View { showBackupAlert = true return } + var entries = [ + FileExport.BundleEntry(name: "report.txt", data: Data(live.exportableLogText().utf8)), + FileExport.BundleEntry(name: "raw-capture.jsonl", data: (try? Data(contentsOf: capture)) ?? Data()), + ] + entries = entries.filter { !$0.data.isEmpty } + let redacted = TestBundleAssembler.redactEntries(entries) + let (capped, _) = TestBundleAssembler.capEntries(redacted) let stamp = FileExport.timestamp() - await FileExport.exportPair( - file: capture, fileSuggestedName: "noop-raw-capture-\(stamp).json", - text: live.exportableLogText(), textSuggestedName: "noop-strap-log-\(stamp).txt") + await FileExport.exportBundle(entries: capped, suggestedName: "noop-export-\(stamp).zip") } } diff --git a/android/app/src/main/java/com/noop/ui/LogExport.kt b/android/app/src/main/java/com/noop/ui/LogExport.kt index 393bf062b2..f35ffa4976 100644 --- a/android/app/src/main/java/com/noop/ui/LogExport.kt +++ b/android/app/src/main/java/com/noop/ui/LogExport.kt @@ -7,6 +7,7 @@ import android.widget.Toast import androidx.core.content.FileProvider import com.noop.BuildConfig import com.noop.ble.PuffinExperiment +import com.noop.testcentre.TestBundleAssembler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File @@ -494,8 +495,10 @@ object LogExport { if (!hasCapture) { Toast.makeText(context, noCaptureMsg(context, whoop5Connected, encryptedBond, sharingLog = true), Toast.LENGTH_LONG).show() } + val redacted = TestBundleAssembler.redactEntries(entries) + val (capped, _) = TestBundleAssembler.capEntries(redacted) val name = "noop-export-${timestamp()}.zip" - exportBundle(context, entries, name) + exportBundle(context, capped, name) }.onFailure { Toast.makeText(context, "Couldn't export the pair: ${it.message}", Toast.LENGTH_LONG).show() } diff --git a/android/app/src/main/java/com/noop/ui/TestCentreScreen.kt b/android/app/src/main/java/com/noop/ui/TestCentreScreen.kt index 016d533b0f..2618a127c9 100644 --- a/android/app/src/main/java/com/noop/ui/TestCentreScreen.kt +++ b/android/app/src/main/java/com/noop/ui/TestCentreScreen.kt @@ -111,6 +111,7 @@ fun TestCentreScreen(vm: AppViewModel, onOpenGroundTruthCollector: () -> Unit = val ecgVariant by vm.ble.whoop5VariantFlow.collectAsStateWithLifecycle() var rawCaptureBusy by remember { mutableStateOf(false) } var rawAndLogBusy by remember { mutableStateOf(false) } + var showRawExportConfirm by remember { mutableStateOf(false) } // A report awaiting the mandatory review-before-share gate (spec section 12). Non-null shows the // review dialog; confirming runs TestReportFlow.run. @@ -361,23 +362,45 @@ fun TestCentreScreen(vm: AppViewModel, onOpenGroundTruthCollector: () -> Unit = kind = NoopButtonKind.Secondary, fullWidth = true, enabled = !rawAndLogBusy, - onClick = { - rawAndLogBusy = true - scope.launch { - try { - LogExport.shareRawAndLog( - context, vm.ble.exportLogText(), live.whoop5Detected, live.encryptedBond, - ) - } finally { - rawAndLogBusy = false - } - } - }, + onClick = { showRawExportConfirm = true }, ) } } } + if (showRawExportConfirm) { + AlertDialog( + onDismissRequest = { showRawExportConfirm = false }, + title = { Text(stringResource(R.string.l10n_test_centre_screen_review_before_sharing_d7050383)) }, + text = { Text(stringResource(R.string.raw_diag_export_review_body)) }, + confirmButton = { + TextButton(onClick = { + showRawExportConfirm = false + rawAndLogBusy = true + scope.launch { + try { + LogExport.shareRawAndLog( + context, + vm.ble.exportLogText(), + live.whoop5Detected, + live.encryptedBond, + ) + } finally { + rawAndLogBusy = false + } + } + }) { + Text(stringResource(R.string.l10n_test_centre_screen_export_f3e4fadb)) + } + }, + dismissButton = { + TextButton(onClick = { showRawExportConfirm = false }) { + Text(stringResource(R.string.l10n_test_centre_screen_cancel_77dfd213)) + } + }, + ) + } + // --- Section 3: Export and auto-export --- ExportCard( vm = vm, diff --git a/android/app/src/main/res/values-de/strings.xml b/android/app/src/main/res/values-de/strings.xml index 036ff981a5..db723e3d4a 100644 --- a/android/app/src/main/res/values-de/strings.xml +++ b/android/app/src/main/res/values-de/strings.xml @@ -964,6 +964,7 @@ Fehler mit meinem Log melden Bericht Vor dem Teilen prüfen + Dieser Export enthält biometrische Rohdaten, Konsolentext des Armbands und das Verbindungsprotokoll. Kennungen werden automatisch geschwärzt. Teile ihn nur, wenn du damit einverstanden bist. Teilen Share strap log (für bug-reports) Testzentrum diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml index 98adbaff13..60f0657b6e 100644 --- a/android/app/src/main/res/values-es/strings.xml +++ b/android/app/src/main/res/values-es/strings.xml @@ -764,6 +764,7 @@ Reportar un error con mi registro Informe Revisa antes de compartir + Esta exportación contiene tramas biométricas sin procesar, texto de la consola de la pulsera y el registro de conexión. Los identificadores se ocultan automáticamente. Compártela solo si te sientes cómodo haciéndolo. Compartir Compartir tronco de correa (para informes de fallos) Centro de pruebas diff --git a/android/app/src/main/res/values-fr/strings.xml b/android/app/src/main/res/values-fr/strings.xml index ba5df808e6..1ee07b8b65 100644 --- a/android/app/src/main/res/values-fr/strings.xml +++ b/android/app/src/main/res/values-fr/strings.xml @@ -782,6 +782,7 @@ Signaler un bug avec mon journal Signaler Vérifier avant de partager + Cette exportation contient des trames biométriques brutes, le texte de la console du bracelet et le journal de connexion. Les identifiants sont automatiquement masqués. Ne la partagez que si cela vous convient. Partager Partager le journal du bracelet (pour les rapports de bogue) Centre de test diff --git a/android/app/src/main/res/values-pl/strings.xml b/android/app/src/main/res/values-pl/strings.xml index 9a50e09f5c..34876f7f1b 100644 --- a/android/app/src/main/res/values-pl/strings.xml +++ b/android/app/src/main/res/values-pl/strings.xml @@ -915,6 +915,7 @@ Zgłoś błąd w moim logu Raport Przejrzyj przed udostępnieniem + Ten eksport zawiera surowe ramki danych biometrycznych, tekst konsoli opaski i dziennik połączenia. Identyfikatory są automatycznie ukrywane. Udostępnij go tylko, jeśli czujesz się z tym komfortowo. Udział Udostępnij dziennik paska (w przypadku raportów o błędach) Centrum Testowe diff --git a/android/app/src/main/res/values-pt-rPT/strings.xml b/android/app/src/main/res/values-pt-rPT/strings.xml index 0b85e0972c..caf14b7777 100644 --- a/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/android/app/src/main/res/values-pt-rPT/strings.xml @@ -929,6 +929,7 @@ Reportar um erro com o meu registo Relatório Reveja antes de partilhar + Esta exportação contém tramas biométricas em bruto, texto da consola da bracelete e o registo de ligação. Os identificadores são ocultados automaticamente. Partilha-a apenas se estiveres confortável com isso. Partilhar Partilhar o registo da bracelete (para relatórios de erros) Centro de testes diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml index 3eb753c149..60a95c8540 100644 --- a/android/app/src/main/res/values-ru/strings.xml +++ b/android/app/src/main/res/values-ru/strings.xml @@ -959,6 +959,7 @@ Сообщить об ошибке, приложив журнал Отчёт Проверьте перед отправкой + Этот экспорт содержит необработанные биометрические кадры, текст консоли браслета и журнал подключения. Идентификаторы автоматически скрываются. Делитесь им, только если вам это комфортно. Поделиться Поделиться журналом браслета (для отчётов об ошибках) Тестовый центр diff --git a/android/app/src/main/res/values-zh/strings.xml b/android/app/src/main/res/values-zh/strings.xml index ec8d607834..52c97870ec 100644 --- a/android/app/src/main/res/values-zh/strings.xml +++ b/android/app/src/main/res/values-zh/strings.xml @@ -907,6 +907,7 @@ 附带日志报告 Bug 报告 在分享前先预览 + 此导出包含原始生物识别数据帧、手环控制台文本和连接日志。标识符会自动隐去。请仅在你对此感到放心时分享。 分享 / 发送 分享手环连接日志 (用于排查 Bug) 测试与极客中心 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 6902a95728..91f8c6a5f8 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1056,6 +1056,7 @@ Report a bug with my log Report Review before sharing + This export contains raw biometric frames, strap console text, and the connection log. Identifiers are redacted automatically. Share it only if you are comfortable with that. Share Share strap log (for bug reports) Test Centre