Fix #37: Privacy-Compliance + Onboarding-Kontaktpflicht
- NSContactsUsageDescription + NSPhotoLibraryUsageDescription in Info.plist ergänzt (App-Store-Review-Compliance für import Contacts/PhotosUI) - Onboarding: Überspringen-Button entfernt, mindestens 1 Kontakt erforderlich - Hinweistext wenn Kontaktliste leer: erklärt warum Weiter gesperrt ist - Alert wenn Picker-Auswahl das Free-Tier-Limit von 3 überschreitet - Lokalisierung (DE+EN) für alle neuen Strings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -715,6 +715,8 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = nahbar;
|
||||
INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "nahbar liest deine Kalender, damit du beim Erstellen eines Termins den richtigen Kalender wählen kannst.";
|
||||
INFOPLIST_KEY_NSCalendarsWriteOnlyAccessUsageDescription = "nahbar erstellt Kalendereinträge für geplante Treffen";
|
||||
INFOPLIST_KEY_NSContactsUsageDescription = "nahbar öffnet den systemseitigen Kontakte-Picker, damit du Personen schnell aus deinem Adressbuch hinzufügen kannst. Die App liest deine Kontakte nicht selbstständig aus.";
|
||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "nahbar öffnet den systemseitigen Foto-Picker, damit du ein Profilbild für eine Person auswählen kannst.";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@@ -756,6 +758,8 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = nahbar;
|
||||
INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "nahbar liest deine Kalender, damit du beim Erstellen eines Termins den richtigen Kalender wählen kannst.";
|
||||
INFOPLIST_KEY_NSCalendarsWriteOnlyAccessUsageDescription = "nahbar erstellt Kalendereinträge für geplante Treffen";
|
||||
INFOPLIST_KEY_NSContactsUsageDescription = "nahbar öffnet den systemseitigen Kontakte-Picker, damit du Personen schnell aus deinem Adressbuch hinzufügen kannst. Die App liest deine Kontakte nicht selbstständig aus.";
|
||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "nahbar öffnet den systemseitigen Foto-Picker, damit du ein Profilbild für eine Person auswählen kannst.";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
|
||||
@@ -228,6 +228,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"%lld Kontakte wurden nicht hinzugefügt. Im Free-Tier kannst du beim Onboarding bis zu 3 Personen auswählen." : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld contacts were not added. In the Free Tier, you can select up to 3 people during onboarding."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%lld Schritte" : {
|
||||
"comment" : "SettingsView – tour step count label (e.g. '6 Schritte')",
|
||||
"extractionState" : "stale",
|
||||
@@ -364,6 +374,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"1 Kontakt wurde nicht hinzugefügt. Im Free-Tier kannst du beim Onboarding bis zu 3 Personen auswählen." : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "1 contact was not added. In the Free Tier, you can select up to 3 people during onboarding."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"1 Monat" : {
|
||||
"comment" : "Settings – look-ahead / period picker option",
|
||||
"localizations" : {
|
||||
@@ -1821,6 +1841,7 @@
|
||||
}
|
||||
},
|
||||
"Du kannst Kontakte jederzeit später in der App hinzufügen." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -2533,6 +2554,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Füge mindestens eine Person hinzu, um fortzufahren – sonst macht nahbar leider keinen Sinn." : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add at least one person to continue – otherwise nahbar unfortunately doesn't make sense."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Füge Personen hinzu, die dir wichtig sind. Notiere Interessen, Gesprächsthemen und was euch verbindet." : {
|
||||
"comment" : "TourCatalog – onboarding step 2 body",
|
||||
"localizations" : {
|
||||
@@ -3515,6 +3546,7 @@
|
||||
}
|
||||
},
|
||||
"Kontakte überspringen" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -3525,6 +3557,7 @@
|
||||
}
|
||||
},
|
||||
"Kontakte überspringen?" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -5754,6 +5787,7 @@
|
||||
}
|
||||
},
|
||||
"Trotzdem überspringen" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@@ -6679,6 +6713,7 @@
|
||||
}
|
||||
},
|
||||
"Zeigt eine Bestätigungsabfrage." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
|
||||
@@ -31,8 +31,7 @@ struct OnboardingContainerView: View {
|
||||
|
||||
OnboardingContactImportView(
|
||||
coordinator: coordinator,
|
||||
onContinue: startPrivacyScreen,
|
||||
onSkip: startPrivacyScreen
|
||||
onContinue: startPrivacyScreen
|
||||
)
|
||||
.tag(1)
|
||||
}
|
||||
@@ -345,10 +344,10 @@ private struct OnboardingProfileView: View {
|
||||
private struct OnboardingContactImportView: View {
|
||||
@ObservedObject var coordinator: OnboardingCoordinator
|
||||
let onContinue: () -> Void
|
||||
let onSkip: () -> Void
|
||||
|
||||
@State private var showingPicker = false
|
||||
@State private var showSkipConfirmation: Bool = false
|
||||
@State private var showingLimitAlert = false
|
||||
@State private var droppedByLimit = 0
|
||||
|
||||
private let maxContacts = 3
|
||||
private var atLimit: Bool { coordinator.selectedContacts.count >= maxContacts }
|
||||
@@ -407,29 +406,21 @@ private struct OnboardingContactImportView: View {
|
||||
: "\(coordinator.selectedContacts.count) Kontakte ausgewählt. Weiter."
|
||||
)
|
||||
|
||||
Button {
|
||||
showSkipConfirmation = true
|
||||
} label: {
|
||||
Text("Überspringen")
|
||||
.font(.subheadline)
|
||||
if coordinator.selectedContacts.isEmpty {
|
||||
Text("Füge mindestens eine Person hinzu, um fortzufahren – sonst macht nahbar leider keinen Sinn.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.accessibilityLabel("Kontakte überspringen")
|
||||
.accessibilityHint("Zeigt eine Bestätigungsabfrage.")
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 16)
|
||||
}
|
||||
|
||||
.confirmationDialog(
|
||||
"Kontakte überspringen?",
|
||||
isPresented: $showSkipConfirmation,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Trotzdem überspringen", role: .destructive, action: onSkip)
|
||||
Button("Abbrechen", role: .cancel) {}
|
||||
.alert("Limit erreicht", isPresented: $showingLimitAlert) {
|
||||
Button("OK", role: .cancel) {}
|
||||
} message: {
|
||||
Text("Du kannst Kontakte jederzeit später in der App hinzufügen.")
|
||||
Text(limitAlertMessage)
|
||||
}
|
||||
.overlay(alignment: .center) {
|
||||
// Invisible trigger — finds the hosting UIViewController via
|
||||
@@ -519,16 +510,34 @@ private struct OnboardingContactImportView: View {
|
||||
|
||||
// MARK: Merge helper
|
||||
|
||||
private var limitAlertMessage: String {
|
||||
if droppedByLimit == 1 {
|
||||
return String(localized: "1 Kontakt wurde nicht hinzugefügt. Im Free-Tier kannst du beim Onboarding bis zu 3 Personen auswählen.")
|
||||
}
|
||||
return String.localizedStringWithFormat(
|
||||
String(localized: "%lld Kontakte wurden nicht hinzugefügt. Im Free-Tier kannst du beim Onboarding bis zu 3 Personen auswählen."),
|
||||
droppedByLimit
|
||||
)
|
||||
}
|
||||
|
||||
/// Merges newly picked contacts into the existing selection (no duplicates).
|
||||
private func mergeContacts(_ contacts: [CNContact]) {
|
||||
var dropped = 0
|
||||
for contact in contacts {
|
||||
guard coordinator.selectedContacts.count < maxContacts else { break }
|
||||
if coordinator.selectedContacts.count >= maxContacts {
|
||||
dropped += 1
|
||||
continue
|
||||
}
|
||||
let alreadySelected = coordinator.selectedContacts
|
||||
.contains { $0.cnIdentifier == contact.identifier }
|
||||
if !alreadySelected {
|
||||
coordinator.selectedContacts.append(NahbarContact(from: contact))
|
||||
}
|
||||
}
|
||||
if dropped > 0 {
|
||||
droppedByLimit = dropped
|
||||
showingLimitAlert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user