Skip to content

feat: block adult content - #79

Merged
ykisana merged 1 commit into
normalengineering:mainfrom
archietownsend:feat/block-adult-content
Oct 26, 2026
Merged

ykisana merged 1 commit into
normalengineering:mainfrom
archietownsend:feat/block-adult-content

Conversation

@archietownsend

@archietownsend archietownsend commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Adds a "Block Adult Websites" toggle in Settings, under a new Adult Content section.

  • Uses Apple's web content filter (webContent.blockedByFilter = .auto(customDomains)) in the main ManagedSettingsStore, merged with custom domains through the shield reconciler. It stays in one store because iOS doesn't document how .auto and .specific filters combine across named stores.
  • Independent of Block All and not counted as an active block, so schedules, groups and other settings stay editable while it's on.
  • Turning it on needs no key; turning it off requires a key, like Prevent App Deletion.
  • Emergency Unblock lifts it.
  • The applied filter is the source of truth, mirrored to the App Group so the monitor's reconciles keep it.
  • Adds a shared KeyProtectedToggle component, used by both the adult content and Prevent App Deletion toggles.
  • README updated.

Needs an on-device check (the filter is a no-op in the simulator): adult sites and custom domains are both blocked, and turning it off removes only the adult filter.

@ykisana

ykisana commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Hey, thanks for the PR @archietownsend! This is a feature I'd love to add, and I do like the toggle system, like custom domains.

Few things (Suggested by Claude)

  1. The adult filter may stop custom domains from being blocked (AdultContentFilter.swift:22). Can we have tests for this scenario?
  2. The filter object isn't kept between redraws (AppContainer.swift:18). It should be held in @State like the other services, otherwise UI tests lose its state.
  3. A missing settings row turns the filter off at launch (AppContainer.swift:46). The ?? false fallback switches the filter off if settings haven't loaded yet.
  4. Only this one view applies the filter (AppContainer.swift:52). Shortcuts running without the app's UI, remove-all-restrictions, and re-authorization never touch it.
  5. Cleanup (optional): the new protocol duplicates ShieldStoring, and the activeShieldCount() check runs twice per redraw.

If you don't have time to update, I'll work on top of this pr and get it ready by tn.

@archietownsend

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Pushed an update:

  • The adult filter now goes through ShieldPlan / ShieldReconciler, so custom domains and adult content share one web filter policy (.auto(customDomains) when on, .specific otherwise). Added tests in WebFilterTests and ShieldReconcilerTests covering both together.
  • Removed the separate filter object; the setting is mirrored to the shared store and applied on reconcile, so it's covered everywhere reconcile runs (shortcuts, monitor extension, re-authorisation).
  • No more ?? false at launch - it only applies once the settings row exists.
  • Remove-all-restrictions clears everything else but keeps the adult filter on, since it's a setting rather than a block. Happy to change that if you'd prefer it cleared too.
  • Dropped the extra protocol and the double activeShieldCount() call.

@ykisana

ykisana commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Thanks for the review! Pushed an update:

* The adult filter now goes through ShieldPlan / ShieldReconciler, so custom domains and adult content share one web filter policy (`.auto(customDomains)` when on, `.specific` otherwise). Added tests in WebFilterTests and ShieldReconcilerTests covering both together.

* Removed the separate filter object; the setting is mirrored to the shared store and applied on reconcile, so it's covered everywhere reconcile runs (shortcuts, monitor extension, re-authorisation).

* No more `?? false` at launch - it only applies once the settings row exists.

* Remove-all-restrictions clears everything else but keeps the adult filter on, since it's a setting rather than a block. Happy to change that if you'd prefer it cleared too.

* Dropped the extra protocol and the double activeShieldCount() call.

Thanks for the update! After thinking about it, I don't think this should work like an app, where it's blocked by keeping all apps unblocked.

I'm thinking let's make this it's own toggle, similar to deleting apps. It makes sense because it's a separate setting in screen-time, like blocking delete apps. Can expand this further, for similar settings if this comes up.

I'm going merge this in, and going to add a second commit building on top this.

- "Block Adult Websites" toggle in Settings using Apple's web content filter (.auto), merged with custom domains in the main store via the shield reconciler
- Turning it off requires a key, like Prevent App Deletion; independent of Block All and not counted as an active block
- Emergency Unblock lifts the filter
- Extract KeyProtectedToggle shared by the adult content and Prevent App Deletion toggles

Co-authored-by: ykisana <ykisana@protonmail.com>
@ykisana
ykisana force-pushed the feat/block-adult-content branch from 00d53d0 to a6af941 Compare October 26, 2026 04:11
@ykisana
ykisana merged commit 5d527e5 into normalengineering:main Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants