b04da850e4
App Store guideline 1.2 requires four UGC controls; this lands three of them and updates the privacy policy with the 24 h response commitment. EULA gate: a fullScreenCover is shown over RootView whenever the stored eulaAcceptedVersion is below EULAView.currentVersion. The view links to the Apple Standard EULA and spells out the project-specific zero-tolerance clause that the rejection letter quoted verbatim. Bumping currentVersion forces re-acceptance after the terms change. Settings -> About -> "Terms of Use" reopens the same view in read-only mode. Block accounts: BlockList is an @Observable singleton that persists a JSON array of FollowCoordinator-normalised feed URLs in UserDefaults. ProfileView gains a Block / Unblock button next to View social.org for non-own profiles. Settings exposes the list with an Unblock action per row. Filtering happens at render time in TimelineView and NotificationsView so blocking from a profile removes content from the feed instantly, as the rejection letter requires; load-time filtering in the view models stays as defense in depth. PRIVACY.md grows a "Moderation and reports" section that names the three tools, commits to acting on reports within 24 hours, and restates the zero-tolerance policy. Apple expects this text in the public privacy policy; the same wording will go into the App Review Information Notes when we resubmit. Report post and the in-app mirror of the 24 h paragraph remain open; both depend on the developer-notification channel decision.
91 lines
3.9 KiB
Swift
91 lines
3.9 KiB
Swift
import SwiftUI
|
|
|
|
/// Terms of Use modal shown before any user-generated content is reachable.
|
|
/// Required by App Store guideline 1.2: "users agree to terms (EULA) and
|
|
/// these terms must make it clear that there is no tolerance for
|
|
/// objectionable content or abusive users".
|
|
///
|
|
/// Acceptance is recorded as `eulaAcceptedVersion` in `UserDefaults`. Bumping
|
|
/// `currentVersion` forces re-acceptance after the terms change.
|
|
struct EULAView: View {
|
|
/// Bumped when the project-specific paragraph below changes; keeps the
|
|
/// gate behaviour explicit instead of hashing the body text.
|
|
static let currentVersion: Int = 1
|
|
|
|
/// `nil` in the gate flow (full screen, requires acceptance).
|
|
/// Non-nil in read-only mode from Settings (just a Close button).
|
|
var onDismiss: (() -> Void)?
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
Text("Terms of Use")
|
|
.font(.title.weight(.bold))
|
|
|
|
Text("By using Org Social you agree to the following:")
|
|
.font(.headline)
|
|
|
|
Text("""
|
|
Org Social is a federated client for plain-text social.org \
|
|
feeds hosted on servers chosen by the user. The app does \
|
|
not host, index or store any of the content shown in the \
|
|
timeline; reads come from public URLs you explicitly \
|
|
follow, and writes go to the server you configured.
|
|
|
|
Zero tolerance for objectionable content or abusive users. \
|
|
Posting threats, harassment, hate speech, sexual content \
|
|
involving minors, doxxing, intellectual-property \
|
|
infringement, or content that violates applicable law \
|
|
is grounds for being blocked and reported. We act on \
|
|
reports of objectionable content within 24 hours by \
|
|
removing the offending post from our local index and \
|
|
ejecting the author from features that depend on us.
|
|
|
|
Filtering, blocking and reporting tools are available \
|
|
inside the app:
|
|
\u{2022} Settings \u{2192} Muted words: keyword filter
|
|
\u{2022} Each profile: Block this account
|
|
\u{2022} Each post menu: Report this post
|
|
|
|
The Apple Standard EULA also applies. Read the full \
|
|
text at the link below.
|
|
""")
|
|
.font(.body)
|
|
|
|
Link("Apple Standard EULA",
|
|
destination: URL(string: "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/")!)
|
|
.font(.body.weight(.medium))
|
|
|
|
Spacer(minLength: 0)
|
|
}
|
|
.padding(20)
|
|
}
|
|
.toolbar {
|
|
if let onDismiss {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Close") { onDismiss() }
|
|
}
|
|
}
|
|
}
|
|
.safeAreaInset(edge: .bottom) {
|
|
if onDismiss == nil {
|
|
Button {
|
|
UserDefaults.standard.set(EULAView.currentVersion, forKey: "eulaAcceptedVersion")
|
|
} label: {
|
|
Text("I Agree")
|
|
.font(.body.weight(.semibold))
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 12)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.padding(.horizontal, 20)
|
|
.padding(.bottom, 12)
|
|
.background(.thinMaterial)
|
|
}
|
|
}
|
|
}
|
|
.interactiveDismissDisabled(onDismiss == nil)
|
|
}
|
|
}
|