Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add counter and 'all' switch to DNS content blockers #6800

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -128,6 +128,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
Loading