Skip to content

Commit

Permalink
Add counter and 'all' switch to DNS content blockers
Browse files Browse the repository at this point in the history
  • Loading branch information
rablador committed Sep 17, 2024
1 parent aa61b9a commit f555080
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 51 deletions.
1 change: 1 addition & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ public enum AccessibilityIdentifier: String {
case wireGuardPort

// Custom DNS
case blockAll
case blockAdvertising
case blockTracking
case blockMalware
Expand Down
5 changes: 4 additions & 1 deletion ios/MullvadVPN/View controllers/Settings/SettingsCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
}
}

private var subCellLeadingIndentation: CGFloat = 0
private let buttonWidth: CGFloat = 24
private let infoButton: UIButton = {
let button = UIButton(type: .custom)
Expand All @@ -85,6 +86,8 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
infoButton.isHidden = true
infoButton.addTarget(self, action: #selector(handleInfoButton(_:)), for: .touchUpInside)

subCellLeadingIndentation = contentView.layoutMargins.left + UIMetrics.TableView.cellIndentationWidth

titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.font = UIFont.systemFont(ofSize: 17)
titleLabel.textColor = UIColor.Cell.titleTextColor
Expand Down Expand Up @@ -149,7 +152,7 @@ class SettingsCell: UITableViewCell, CustomCellDisclosureHandling {
}

func applySubCellStyling() {
contentView.layoutMargins.left += UIMetrics.TableView.cellIndentationWidth
contentView.layoutMargins.left = subCellLeadingIndentation
backgroundView?.backgroundColor = UIColor.Cell.Background.indentationLevelOne
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class CustomDNSCellFactory: CellFactoryProtocol {
cell.titleLabel.text = title
cell.accessibilityIdentifier = preference.accessibilityIdentifier
cell.applySubCellStyling()
cell.setOn(toggleSetting, animated: false)
cell.setOn(toggleSetting, animated: true)
cell.action = { [weak self] isOn in
self?.delegate?.didChangeState(
for: preference,
Expand All @@ -58,6 +58,21 @@ final class CustomDNSCellFactory: CellFactoryProtocol {
// swiftlint:disable:next function_body_length
func configureCell(_ cell: UITableViewCell, item: CustomDNSDataSource.Item, indexPath: IndexPath) {
switch item {
case .blockAll:
let localizedString = NSLocalizedString(
"BLOCK_ALL_CELL_LABEL",
tableName: "VPNSettings",
value: "All",
comment: ""
)

configure(
cell,
toggleSetting: viewModel.blockAll,
title: localizedString,
for: .blockAll
)

case .blockAdvertising:
let localizedString = NSLocalizedString(
"BLOCK_ADS_CELL_LABEL",
Expand Down
112 changes: 65 additions & 47 deletions ios/MullvadVPN/View controllers/VPNSettings/CustomDNSDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource<
}

enum Item: Hashable {
case blockAll
case blockAdvertising
case blockTracking
case blockMalware
Expand All @@ -61,11 +62,21 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource<
case dnsServerInfo

static var contentBlockers: [Item] {
[.blockAdvertising, .blockTracking, .blockMalware, .blockGambling, .blockAdultContent, .blockSocialMedia]
[
.blockAll,
.blockAdvertising,
.blockTracking,
.blockMalware,
.blockGambling,
.blockAdultContent,
.blockSocialMedia,
]
}

var accessibilityIdentifier: AccessibilityIdentifier {
switch self {
case .blockAll:
return .blockAll
case .blockAdvertising:
return .blockAdvertising
case .blockTracking:
Expand Down Expand Up @@ -403,85 +414,74 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource<
}
}

private func setBlockAdvertising(_ isEnabled: Bool) {
private func setBlockAll(_ isEnabled: Bool) {
let oldViewModel = viewModel
viewModel.setBlockAll(isEnabled)
reloadBlockerData(isEnabled, oldViewModel: oldViewModel)

viewModel.setBlockAdvertising(isEnabled)

if oldViewModel.customDNSPrecondition != viewModel.customDNSPrecondition {
reloadDnsServerInfo()
[
.blockAdvertising,
.blockTracking,
.blockMalware,
.blockAdultContent,
.blockGambling,
.blockSocialMedia,
].forEach { item in
reload(item: item)
}
}

if !isEditing {
delegate?.didChangeViewModel(viewModel)
}
private func setBlockAdvertising(_ isEnabled: Bool) {
let oldViewModel = viewModel
viewModel.setBlockAdvertising(isEnabled)
reloadBlockerData(isEnabled, oldViewModel: oldViewModel)
}

private func setBlockTracking(_ isEnabled: Bool) {
let oldViewModel = viewModel

viewModel.setBlockTracking(isEnabled)

if oldViewModel.customDNSPrecondition != viewModel.customDNSPrecondition {
reloadDnsServerInfo()
}

if !isEditing {
delegate?.didChangeViewModel(viewModel)
}
reloadBlockerData(isEnabled, oldViewModel: oldViewModel)
}

private func setBlockMalware(_ isEnabled: Bool) {
let oldViewModel = viewModel

viewModel.setBlockMalware(isEnabled)

if oldViewModel.customDNSPrecondition != viewModel.customDNSPrecondition {
reloadDnsServerInfo()
}

if !isEditing {
delegate?.didChangeViewModel(viewModel)
}
reloadBlockerData(isEnabled, oldViewModel: oldViewModel)
}

private func setBlockAdultContent(_ isEnabled: Bool) {
let oldViewModel = viewModel

viewModel.setBlockAdultContent(isEnabled)

if oldViewModel.customDNSPrecondition != viewModel.customDNSPrecondition {
reloadDnsServerInfo()
}

if !isEditing {
delegate?.didChangeViewModel(viewModel)
}
reloadBlockerData(isEnabled, oldViewModel: oldViewModel)
}

private func setBlockGambling(_ isEnabled: Bool) {
let oldViewModel = viewModel

viewModel.setBlockGambling(isEnabled)

if oldViewModel.customDNSPrecondition != viewModel.customDNSPrecondition {
reloadDnsServerInfo()
}

if !isEditing {
delegate?.didChangeViewModel(viewModel)
}
reloadBlockerData(isEnabled, oldViewModel: oldViewModel)
}

private func setBlockSocialMedia(_ isEnabled: Bool) {
let oldViewModel = viewModel

viewModel.setBlockSocialMedia(isEnabled)
reloadBlockerData(isEnabled, oldViewModel: oldViewModel)
}

private func reloadBlockerData(_ isEnabled: Bool, oldViewModel: VPNSettingsViewModel) {
if oldViewModel.customDNSPrecondition != viewModel.customDNSPrecondition {
reloadDnsServerInfo()
}

if !isEnabled || viewModel.allBlockersEnabled {
reload(item: .blockAll)
}

if
let index = snapshot().sectionIdentifiers.firstIndex(of: .contentBlockers),
let headerView = tableView?.headerView(forSection: index) as? SettingsHeaderView {
configureContentBlockersHeader(headerView)
}

if !isEditing {
delegate?.didChangeViewModel(viewModel)
}
Expand Down Expand Up @@ -588,8 +588,23 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource<
comment: ""
)

header.titleLabel.text = title
let enabledBlockersCount = viewModel.enabledBlockersCount
let attributedTitle = NSMutableAttributedString(string: title)
let blockerCountText = NSAttributedString(string: " (\(enabledBlockersCount))", attributes: [
.foregroundColor: UIColor.primaryTextColor.withAlphaComponent(0.6),
])

if enabledBlockersCount > 0 {
attributedTitle.append(blockerCountText)
}

UIView.transition(with: header.titleLabel, duration: 0.2, options: .transitionCrossDissolve) {
header.titleLabel.attributedText = attributedTitle
}
header.titleLabel.sizeToFit()

header.accessibilityCustomActionName = title
header.accessibilityValue = "\(enabledBlockersCount)"
header.accessibilityIdentifier = .dnsContentBlockersHeaderView

header.infoButtonHandler = { [weak self] in
Expand Down Expand Up @@ -618,6 +633,9 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource<
extension CustomDNSDataSource: CustomDNSCellEventHandler {
func didChangeState(for preference: Item, isOn: Bool) {
switch preference {
case .blockAll:
setBlockAll(isOn)

case .blockAdvertising:
setBlockAdvertising(isOn)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ extension VPNSettingsViewController: VPNSettingsDataSourceDelegate {
interactor.evaluateDaitaSettingsCompatibility(settings)
}

// swiftlint:disable:next function_body_length
func showPrompt(
for item: VPNSettingsPromptAlertItem,
onSave: @escaping () -> Void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ struct DNSServerEntry: Equatable, Hashable {
}

struct VPNSettingsViewModel: Equatable {
private(set) var blockAll: Bool
private(set) var blockAdvertising: Bool
private(set) var blockTracking: Bool
private(set) var blockMalware: Bool
Expand All @@ -104,39 +105,69 @@ struct VPNSettingsViewModel: Equatable {

static let defaultWireGuardPorts: [UInt16] = [51820, 53]

var enabledBlockersCount: Int {
[
blockAdvertising,
blockTracking,
blockMalware,
blockAdultContent,
blockGambling,
blockSocialMedia,
].filter { $0 }.count
}

var allBlockersEnabled: Bool {
enabledBlockersCount == CustomDNSDataSource.Item.contentBlockers.filter { $0 != .blockAll }.count
}

mutating func setBlockAll(_ newValue: Bool) {
blockAll = newValue
blockAdvertising = newValue
blockTracking = newValue
blockMalware = newValue
blockAdultContent = newValue
blockGambling = newValue
blockSocialMedia = newValue
enableCustomDNS = false
}

mutating func setBlockAdvertising(_ newValue: Bool) {
blockAdvertising = newValue
blockAll = allBlockersEnabled
enableCustomDNS = false
}

mutating func setBlockTracking(_ newValue: Bool) {
blockTracking = newValue
blockAll = allBlockersEnabled
enableCustomDNS = false
}

mutating func setBlockMalware(_ newValue: Bool) {
blockMalware = newValue
blockAll = allBlockersEnabled
enableCustomDNS = false
}

mutating func setBlockAdultContent(_ newValue: Bool) {
blockAdultContent = newValue
blockAll = allBlockersEnabled
enableCustomDNS = false
}

mutating func setBlockGambling(_ newValue: Bool) {
blockGambling = newValue
blockAll = allBlockersEnabled
enableCustomDNS = false
}

mutating func setBlockSocialMedia(_ newValue: Bool) {
blockSocialMedia = newValue
blockAll = allBlockersEnabled
enableCustomDNS = false
}

mutating func setEnableCustomDNS(_ newValue: Bool) {
blockTracking = false
blockAdvertising = false
enableCustomDNS = newValue
}

Expand Down Expand Up @@ -195,12 +226,20 @@ struct VPNSettingsViewModel: Equatable {

init(from tunnelSettings: LatestTunnelSettings = LatestTunnelSettings()) {
let dnsSettings = tunnelSettings.dnsSettings

blockAdvertising = dnsSettings.blockingOptions.contains(.blockAdvertising)
blockTracking = dnsSettings.blockingOptions.contains(.blockTracking)
blockMalware = dnsSettings.blockingOptions.contains(.blockMalware)
blockAdultContent = dnsSettings.blockingOptions.contains(.blockAdultContent)
blockGambling = dnsSettings.blockingOptions.contains(.blockGambling)
blockSocialMedia = dnsSettings.blockingOptions.contains(.blockSocialMedia)
blockAll = blockAdvertising
&& blockTracking
&& blockMalware
&& blockAdultContent
&& blockGambling
&& blockSocialMedia

enableCustomDNS = dnsSettings.enableCustomDNS
customDNSDomains = dnsSettings.customDNSDomains.map { ipAddress in
DNSServerEntry(identifier: UUID(), address: "\(ipAddress)")
Expand Down

0 comments on commit f555080

Please sign in to comment.