[KB-2177, KB-2193]: Creating a list and View the Lists, delete the list

This commit is contained in:
Aliaksandr Dvoineu
2023-04-11 06:27:13 +03:00
parent 524f095904
commit dae9ba76fc
31 changed files with 2415 additions and 205 deletions

View File

@@ -237,7 +237,7 @@
</objects>
<point key="canvasLocation" x="17" y="591"/>
</scene>
<!--Protect-->
<!--Home-->
<scene sceneID="Js3-kv-Qbd">
<objects>
<viewController storyboardIdentifier="homeViewController" id="NZN-Gg-FQX" customClass="HomeViewController" customModule="Lockdown" customModuleProvider="target" sceneMemberID="viewController">
@@ -967,7 +967,7 @@
</constraint>
</constraints>
</view>
<tabBarItem key="tabBarItem" title="Protect" image="lock.shield.fill" catalog="system" id="O5a-jC-Mj1"/>
<tabBarItem key="tabBarItem" title="Home" image="lock.shield.fill" catalog="system" id="O5a-jC-Mj1"/>
<connections>
<outlet property="allTimeMetrics" destination="C0f-ye-V9U" id="pgQ-k9-Agg"/>
<outlet property="dailyMetrics" destination="XSr-oZ-hkd" id="41Q-JX-CQw"/>
@@ -1165,7 +1165,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="CQN-ao-cuu">
<rect key="frame" x="0.0" y="54" width="375" height="50"/>
<rect key="frame" x="0.0" y="54" width="375" height="48"/>
<subviews>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PO4-d5-rZJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="8"/>
@@ -1186,10 +1186,10 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SRb-dA-tWF">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="48"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Blocking Enabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kd7-nB-tAb">
<rect key="frame" x="20" y="6.5" width="144" height="30"/>
<rect key="frame" x="20" y="5.5" width="144" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="ZgL-en-Q3V"/>
</constraints>
@@ -1197,14 +1197,14 @@
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MMI-Pp-XI4">
<rect key="frame" x="306" y="5" width="51" height="33"/>
<rect key="frame" x="306" y="5" width="51" height="31"/>
<color key="onTintColor" red="0.0" green="0.67751116069999995" blue="0.90461090690000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="toggleLockdownWithSender:" destination="lbX-da-1v3" eventType="valueChanged" id="5KZ-iG-w1a"/>
</connections>
</switch>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pH0-q3-pY2">
<rect key="frame" x="0.0" y="49" width="375" height="1"/>
<rect key="frame" x="0.0" y="47" width="375" height="1"/>
<color key="backgroundColor" systemColor="separatorColor"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="cle-g9-fSo"/>
@@ -1227,7 +1227,7 @@
</subviews>
</stackView>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="45" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="Gjd-0I-N7k">
<rect key="frame" x="0.0" y="104" width="375" height="563"/>
<rect key="frame" x="0.0" y="102" width="375" height="565"/>
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="BlockListGroupCell" rowHeight="72" id="c8H-rF-8EP" customClass="BlockListGroupCell" customModule="Lockdown" customModuleProvider="target">
@@ -3426,7 +3426,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="kzx-wt-LeJ" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-6443" y="1966"/>
<point key="canvasLocation" x="-5738" y="2693"/>
</scene>
<!--Account-->
<scene sceneID="xBZ-K8-q5G">
@@ -3536,8 +3536,8 @@
</designable>
</designables>
<inferredMetricsTieBreakers>
<segue reference="XMa-OM-jKs"/>
<segue reference="QzY-Hs-CoL"/>
<segue reference="f2L-nH-Ive"/>
<segue reference="qGh-4l-Gst"/>
</inferredMetricsTieBreakers>
<resources>
<image name="blue_circle" width="156" height="156"/>

View File

@@ -85,3 +85,14 @@ class BlockListView: UIView {
}
}
}
extension BlockListView.Contents {
static func listsBlocked(listName: String, isEnabled: Bool) -> Self {
let image = UIImage(named: "icn_list_lock")
let status = isEnabled ?
NSLocalizedString("On", comment: "") :
NSLocalizedString("Off", comment: "")
return Self(image: image, title: listName, status: status)
}
}

View File

@@ -91,7 +91,7 @@ class BlockListGroupViewController: BaseViewController, UITableViewDelegate, UIT
}
@IBAction func dismiss() {
blockListVC?.reloadBlockLists()
blockListVC?.reloadCuratedBlockDomains()
self.navigationController?.popViewController(animated: true)
}
}

View File

@@ -8,126 +8,251 @@
import UIKit
import CocoaLumberjackSwift
class BlockListViewController: BaseViewController {
final class BlockListViewController: BaseViewController {
// MARK: - Properties
var didMakeChange = false
var lockdownBlockLists: [LockdownGroup] = []
var userBlockedDomains: [(String, Bool)] = []
var customBlockedDomains: [(String, Bool)] = []
// TODO: - change data structure [[userListBlockedDomains], Bool]
var customBlockedLists: [(String, Bool)] = []
let blockListsTableView = StaticTableView()
let customBlocksTableView = StaticTableView()
let curatedBlockedDomainsTableView = StaticTableView()
let customBlockedListsTableView = StaticTableView()
let customBlockedDomainsTableView = StaticTableView()
private lazy var listsSubmenuView: ListsSubmenuView = {
let view = ListsSubmenuView()
view.createNewListButton.addTarget(self, action: #selector(addList), for: .touchUpInside)
view.importBlockListButton.addTarget(self, action: #selector(importBlockList), for: .touchUpInside)
return view
}()
private lazy var customNavigationView: CustomNavigationView = {
let view = CustomNavigationView()
view.title = NSLocalizedString("Configure Blocking", comment: "")
view.buttonTitle = NSLocalizedString("CLOSE", comment: "")
view.onButtonPressed { [unowned self] in
self.close()
}
return view
}()
enum Page: CaseIterable {
case blockLists
case curated
case custom
var localizedTitle: String {
switch self {
case .blockLists:
return NSLocalizedString("Block Lists", comment: "")
case .curated:
return NSLocalizedString("Curated", comment: "")
case .custom:
return NSLocalizedString("Custom", comment: "")
}
}
}
let segmented = UISegmentedControl(items: Page.allCases.map(\.localizedTitle))
let explanationLabel = UILabel()
let blockListAddView = BlockListAddView()
var addDomainTextField: UITextField {
return blockListAddView.textField
}
private lazy var segmented: UISegmentedControl = {
let view = UISegmentedControl(items: Page.allCases.map(\.localizedTitle))
view.selectedSegmentIndex = 0
view.setTitleTextAttributes([.font: fontMedium14], for: .normal)
view.selectedSegmentTintColor = .tunnelsBlue
view.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
view.addTarget(self, action: #selector(segmentedControlDidChangeValue), for: .valueChanged)
return view
}()
@objc func dismissKeyboard() {
self.view.endEditing(true)
}
private let paragraphLabel: UILabel = {
let view = UILabel()
view.font = fontRegular14
view.numberOfLines = 0
view.text = NSLocalizedString("Block all your apps from connecting to the domains and sites below. For your convenience, Lockdown also has pre-configured suggestions.", comment: "")
return view
}()
private lazy var addNewListButton: UIButton = {
let button = UIButton(type: .system)
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold, scale: .large)
button.tintColor = .tunnelsBlue
button.setImage(UIImage(systemName: "plus", withConfiguration: symbolConfig), for: .normal)
button.addTarget(self, action: #selector(showSubmenu), for: .touchUpInside)
return button
}()
private lazy var listsLabel: UILabel = {
let label = UILabel()
label.text = NSLocalizedString("Lists", comment: "")
label.textColor = .label
label.font = fontBold18
return label
}()
private lazy var emptyListsView: EmptyListsView = {
let view = EmptyListsView()
view.descriptionLabel.text = NSLocalizedString("No lists yet", comment: "")
view.addButton.setTitle(NSLocalizedString("Create a list", comment: ""), for: .normal)
view.addButton.addTarget(self, action: #selector(addList), for: .touchUpInside)
return view
}()
private lazy var domainsLabel: UILabel = {
let label = UILabel()
label.text = NSLocalizedString("Domains", comment: "")
label.textColor = .label
label.font = fontBold18
return label
}()
private lazy var addNewDomainButton: UIButton = {
let button = UIButton(type: .system)
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold, scale: .large)
button.tintColor = .tunnelsBlue
button.setImage(UIImage(systemName: "plus", withConfiguration: symbolConfig), for: .normal)
button.addTarget(self, action: #selector(addDomain), for: .touchUpInside)
return button
}()
private lazy var editDomainButton: UIButton = {
let button = UIButton(type: .system)
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold, scale: .large)
button.tintColor = .tunnelsBlue
button.setImage(UIImage(named: "icn_edit"), for: .normal)
button.addTarget(self, action: #selector(editDomains), for: .touchUpInside)
button.isHidden = true
return button
}()
private lazy var emptyDomainsView: EmptyListsView = {
let view = EmptyListsView()
view.descriptionLabel.text = NSLocalizedString("No custom domains yet", comment: "")
view.addButton.setTitle(NSLocalizedString("Add a domain", comment: ""), for: .normal)
view.addButton.addTarget(self, action: #selector(addDomain), for: .touchUpInside)
return view
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let customNavigationView = CustomNavigationView()
customNavigationView.title = NSLocalizedString("Configure Blocking", comment: "")
customNavigationView.buttonTitle = NSLocalizedString("SAVE", comment: "")
customNavigationView.onButtonPressed { [unowned self] in
self.save()
}
view.backgroundColor = .secondarySystemBackground
configure()
configureCuratedBlockedDomainsTableView()
configureCustomBlockedListsTableView()
configureCustomBlockedDomainsTableView()
}
private func configure() {
view.addSubview(customNavigationView)
customNavigationView.anchors.leading.pin()
customNavigationView.anchors.trailing.pin()
customNavigationView.anchors.top.safeAreaPin()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
view.addSubview(paragraphLabel)
paragraphLabel.anchors.top.spacing(0, to: customNavigationView.anchors.bottom)
paragraphLabel.anchors.leading.readableContentPin(inset: 3)
paragraphLabel.anchors.trailing.readableContentPin(inset: 3)
do {
explanationLabel.font = fontRegular14
explanationLabel.numberOfLines = 0
explanationLabel.text = NSLocalizedString("Block all your apps from connecting to the domains and sites below. For your convenience, Lockdown also has pre-configured suggestions.", comment: "")
view.addSubview(explanationLabel)
explanationLabel.anchors.top.spacing(0, to: customNavigationView.anchors.bottom)
explanationLabel.anchors.leading.readableContentPin(inset: 3)
explanationLabel.anchors.trailing.readableContentPin(inset: 3)
}
do {
view.addSubview(segmented)
segmented.selectedSegmentIndex = 0
segmented.anchors.top.spacing(12, to: explanationLabel.anchors.bottom)
segmented.anchors.leading.readableContentPin()
segmented.anchors.trailing.readableContentPin()
segmented.setTitleTextAttributes([.font: fontMedium14], for: .normal)
if #available(iOS 13.0, *) {
segmented.selectedSegmentTintColor = .tunnelsBlue
segmented.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
}
segmented.addTarget(self, action: #selector(segmentedControlDidChangeValue), for: .valueChanged)
}
do {
addDomainTextField.addTarget(self, action: #selector(textFieldDidEndOnExit), for: .editingDidEndOnExit)
}
do {
addTableView(blockListsTableView, layout: { tableView in
tableView.anchors.top.spacing(8, to: segmented.anchors.bottom)
tableView.anchors.leading.pin()
tableView.anchors.trailing.pin()
tableView.anchors.bottom.pin()
})
reloadBlockLists()
addTableView(customBlocksTableView, layout: { tableView in
tableView.anchors.top.spacing(8, to: segmented.anchors.bottom)
tableView.anchors.leading.pin()
tableView.anchors.trailing.pin()
tableView.anchors.bottom.pin()
})
customBlocksTableView.deselectsCellsAutomatically = true
reloadUserBlockedDomains()
transition(toPage: .blockLists)
}
view.addSubview(segmented)
segmented.anchors.top.spacing(12, to: paragraphLabel.anchors.bottom)
segmented.anchors.leading.readableContentPin()
segmented.anchors.trailing.readableContentPin()
}
func reloadBlockLists() {
blockListsTableView.clear()
private func configureCuratedBlockedDomainsTableView() {
addTableView(curatedBlockedDomainsTableView, layout: { tableView in
tableView.anchors.top.spacing(8, to: segmented.anchors.bottom)
tableView.anchors.leading.pin()
tableView.anchors.trailing.pin()
})
reloadCuratedBlockDomains()
transition(toPage: .curated)
}
private func configureCustomBlockedListsTableView() {
view.addSubview(listsLabel)
listsLabel.anchors.top.spacing(24, to: segmented.anchors.bottom)
listsLabel.anchors.leading.marginsPin()
view.addSubview(addNewListButton)
addNewListButton.anchors.centerY.equal(listsLabel.anchors.centerY)
addNewListButton.anchors.trailing.marginsPin()
addTableView(customBlockedListsTableView, layout: { tableView in
tableView.anchors.top.spacing(0, to: listsLabel.anchors.bottom)
tableView.anchors.leading.pin()
tableView.anchors.trailing.pin()
})
view.addSubview(listsSubmenuView)
listsSubmenuView.anchors.trailing.marginsPin()
listsSubmenuView.anchors.top.spacing(60, to: paragraphLabel.anchors.bottom)
customBlockedListsTableView.deselectsCellsAutomatically = true
reloadCustomBlockedLists()
}
private func configureCustomBlockedDomainsTableView() {
view.addSubview(domainsLabel)
domainsLabel.anchors.top.spacing(16, to: customBlockedListsTableView.anchors.bottom)
domainsLabel.anchors.leading.marginsPin()
view.addSubview(addNewDomainButton)
addNewDomainButton.anchors.centerY.equal(domainsLabel.anchors.centerY)
addNewDomainButton.anchors.trailing.marginsPin()
view.addSubview(editDomainButton)
editDomainButton.anchors.centerY.equal(domainsLabel.anchors.centerY)
editDomainButton.anchors.trailing.spacing(16, to: addNewDomainButton.anchors.leading)
addTableView(customBlockedDomainsTableView, layout: { tableView in
tableView.anchors.top.spacing(0, to: domainsLabel.anchors.bottom)
tableView.anchors.leading.pin()
tableView.anchors.trailing.pin()
})
customBlockedDomainsTableView.deselectsCellsAutomatically = true
reloadCustomBlockedDomains()
}
// Curated lists
func reloadCuratedBlockDomains() {
curatedBlockedDomainsTableView.clear()
lockdownBlockLists = {
let domains = getLockdownBlockedDomains().lockdownDefaults
let sorted = domains.sorted(by: { $0.key < $1.key })
return Array(sorted.map(\.value))
}()
createBlockListsRows()
blockListsTableView.reloadData()
createCuratedBlockedDomainsRows()
curatedBlockedDomainsTableView.reloadData()
}
func reloadUserBlockedDomains() {
customBlocksTableView.clear()
userBlockedDomains = {
func reloadCustomBlockedLists() {
customBlockedListsTableView.clear()
customBlockedLists = {
let lists = getUserBlockedList()
return lists.sorted(by: { $0.key < $1.key }).map { (key, value) -> (String, Bool) in
if let status = value as? NSNumber {
return (key, status.boolValue)
} else {
return (key, false)
}
}
}()
createCustomBlockedListsRows()
customBlockedListsTableView.reloadData()
}
func reloadCustomBlockedDomains() {
customBlockedDomainsTableView.clear()
customBlockedDomains = {
let domains = getUserBlockedDomains()
return domains.sorted(by: { $0.key < $1.key }).map { (key, value) -> (String, Bool) in
if let status = value as? NSNumber {
@@ -137,12 +262,13 @@ class BlockListViewController: BaseViewController {
}
}
}()
createCustomBlocksRows()
customBlocksTableView.reloadData()
createCustomBlockedDomainsRows()
customBlockedDomainsTableView.reloadData()
}
func createBlockListsRows() {
let tableView = blockListsTableView
// Curated Lists
func createCuratedBlockedDomainsRows() {
let tableView = curatedBlockedDomainsTableView
for lockdownGroup in lockdownBlockLists {
@@ -163,15 +289,120 @@ class BlockListViewController: BaseViewController {
}
}
func createCustomBlocksRows() {
let tableView = customBlocksTableView
@objc
func segmentedControlDidChangeValue() {
let page = Page.allCases[segmented.selectedSegmentIndex]
transition(toPage: page)
}
func transition(toPage page: Page) {
tableView.addRow { (contentView) in
contentView.addSubview(blockListAddView)
blockListAddView.anchors.edges.pin()
switch page {
case .curated:
customBlockedDomainsTableView.isHidden = true
customBlockedListsTableView.isHidden = true
curatedBlockedDomainsTableView.isHidden = false
listsLabel.isHidden = true
addNewListButton.isHidden = true
listsSubmenuView.isHidden = true
addNewDomainButton.isHidden = true
domainsLabel.isHidden = true
editDomainButton.isHidden = true
case .custom:
customBlockedListsTableView.isHidden = false
customBlockedDomainsTableView.isHidden = false
curatedBlockedDomainsTableView.isHidden = true
listsLabel.isHidden = false
addNewListButton.isHidden = false
addNewDomainButton.isHidden = false
domainsLabel.isHidden = false
editDomainButton.isHidden = false
}
}
func saveNewList(userEnteredListName: String) {
DDLogInfo("Adding custom list - \(userEnteredListName)")
addUserBlockedList(list: userEnteredListName.lowercased())
reloadCustomBlockedLists()
}
func saveNewDomain(userEnteredDomainName: String) {
let validation = DomainNameValidator.validate(userEnteredDomainName)
switch validation {
case .valid:
didMakeChange = true
DDLogInfo("Adding custom domain - \(userEnteredDomainName)")
addUserBlockedDomain(domain: userEnteredDomainName.lowercased())
reloadCustomBlockedDomains()
case .notValid(let reason):
DDLogWarn("Custom domain is not valid - \(userEnteredDomainName), reason - \(reason)")
showPopupDialog(
title: NSLocalizedString("Invalid domain", comment: ""),
message: "\"\(userEnteredDomainName)\"" + NSLocalizedString(" is not a valid entry. Please only enter the host of the domain you want to block. For example, \"google.com\" without \"https://\"", comment: ""),
acceptButton: NSLocalizedString("Okay", comment: "")
)
}
}
}
// MARK: - Functions
extension BlockListViewController {
func createCustomBlockedListsRows() {
let tableView = customBlockedListsTableView
let emptyList = emptyListsView
if customBlockedLists.count == 0 {
tableView.addRow { (contentView) in
contentView.addSubview(emptyList)
emptyListsView.anchors.edges.pin()
}.onSelect {
self.addList()
}
}
for (domain, isEnabled) in userBlockedDomains {
for (list, status) in customBlockedLists {
// var currentEnabledStatus = status
let blockListView = BlockListView()
blockListView.contents = .listsBlocked(listName: list, isEnabled: status)
let cell = tableView.addRow { (contentView) in
contentView.addSubview(blockListView)
blockListView.anchors.edges.pin()
}.onSelect { [unowned blockListView, unowned self] in
self.didMakeChange = true
let storyboard = UIStoryboard.main
// let target = storyboard.instantiate(BlockListGroupViewController.self)
// target.lockdownGroup = lockdownGroup
// target.blockListVC = self
// self.navigationController?.pushViewController(target, animated: true)
// currentEnabledStatus.toggle()
// blockListView.contents = .listsBlocked(listName: list, isEnabled: currentEnabledStatus)
// setUserBlockedList(list: list, enabled: currentEnabledStatus)
}.onSwipeToDelete { [unowned self] in
self.didMakeChange = true
deleteList(list: list)
DDLogInfo("Deleting custom list - \(list)")
}
cell.accessoryType = .disclosureIndicator
}
}
// Custom Domains
func createCustomBlockedDomainsRows() {
let tableView = customBlockedDomainsTableView
let emptyDomains = emptyDomainsView
if customBlockedDomains.count == 0 {
tableView.addRow { (contentView) in
contentView.addSubview(emptyDomains)
emptyDomains.anchors.edges.pin()
}.onSelect { [unowned self] in
self.addDomain()
}
}
for (domain, isEnabled) in customBlockedDomains {
var currentEnabledStatus = isEnabled
let blockListView = BlockListView()
blockListView.contents = .userBlocked(domain: domain, isEnabled: isEnabled)
@@ -192,62 +423,9 @@ class BlockListViewController: BaseViewController {
}
}
@objc
func segmentedControlDidChangeValue() {
let page = Page.allCases[segmented.selectedSegmentIndex]
transition(toPage: page)
}
func transition(toPage page: Page) {
dismissKeyboard()
switch page {
case .blockLists:
customBlocksTableView.isHidden = true
blockListsTableView.isHidden = false
case .custom:
customBlocksTableView.isHidden = false
blockListsTableView.isHidden = true
}
}
@objc
func textFieldDidEndOnExit(textField: UITextField) {
dismissKeyboard()
guard let text = textField.text else {
DDLogError("Text is empty on add domain text field")
return
}
saveNewDomain(userEnteredDomainName: text)
}
func saveNewDomain(userEnteredDomainName: String) {
let validation = DomainNameValidator.validate(userEnteredDomainName)
switch validation {
case .valid:
didMakeChange = true
DDLogInfo("Adding custom domain - \(userEnteredDomainName)")
addUserBlockedDomain(domain: userEnteredDomainName.lowercased())
addDomainTextField.text = ""
reloadUserBlockedDomains()
case .notValid(let reason):
DDLogWarn("Custom domain is not valid - \(userEnteredDomainName), reason - \(reason)")
showPopupDialog(
title: NSLocalizedString("Invalid domain", comment: ""),
message: "\"\(userEnteredDomainName)\"" + NSLocalizedString(" is not a valid entry. Please only enter the host of the domain you want to block. For example, \"google.com\" without \"https://\"", comment: ""),
acceptButton: NSLocalizedString("Okay", comment: "")
) {
self.addDomainTextField.becomeFirstResponder()
}
}
}
func save() {
self.dismiss(animated: true, completion: {
func close() {
dismiss(animated: true, completion: { [weak self] in
guard let self else { return }
if (self.didMakeChange == true) {
if getIsCombinedBlockListEmpty() {
FirewallController.shared.setEnabled(false, isUserExplicitToggle: true)
@@ -257,4 +435,96 @@ class BlockListViewController: BaseViewController {
}
})
}
@objc func addList() {
let tableView = customBlockedListsTableView
let alertController = UIAlertController(title: "Create New List", message: nil, preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [weak self] (_) in
if let txtField = alertController.textFields?.first, let text = txtField.text {
guard let self else { return }
self.saveNewList(userEnteredListName: text)
if !getUserBlockedList().isEmpty {
tableView.clear()
}
self.reloadCustomBlockedLists()
self.listsSubmenuView.isHidden = true
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { [weak self] (_) in
guard let self else { return }
self.listsSubmenuView.isHidden = true
}
alertController.addTextField { (textField) in
textField.placeholder = NSLocalizedString("List Name", comment: "")
}
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
func deleteList(list: String) {
print("deleteDomains btn pressed ....")
let alert = UIAlertController(title: NSLocalizedString("Delete List?", comment: ""),
message: NSLocalizedString("Are you sure you want to remove this list?", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("No, Return", comment: ""),
style: UIAlertAction.Style.default,
handler: { [weak self] (_) in
guard let self else { return }
self.reloadCustomBlockedLists()
print("Return")
}))
alert.addAction(UIAlertAction(title: NSLocalizedString("Yes, Delete", comment: ""),
style: UIAlertAction.Style.destructive,
handler: { [weak self] (_) in
guard let self else { return }
deleteUserBlockedList(list: list)
self.customBlockedListsTableView.clear()
}))
self.present(alert, animated: true, completion: nil)
}
@objc func showSubmenu() {
listsSubmenuView.isHidden = false
}
@objc func dismissView() {
listsSubmenuView.isHidden = true
}
@objc func importBlockList() {
listsSubmenuView.isHidden = true
print("importBlockList ....")
}
@objc func addDomain() {
print("Alert is on")
let tableView = customBlockedDomainsTableView
let alertController = UIAlertController(title: "Add a Domain to Block", message: nil, preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [weak self] (_) in
if let txtField = alertController.textFields?.first, let text = txtField.text {
guard let self else { return }
self.saveNewDomain(userEnteredDomainName: text)
if !getUserBlockedDomains().isEmpty {
tableView.clear()
}
self.reloadCustomBlockedDomains()
print("Domain==>" + text + "added")
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
alertController.addTextField { (textField) in
textField.placeholder = "domain-to-block URL"
}
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
@objc func editDomains() {
print("editDomains .....")
}
}

View File

@@ -0,0 +1,111 @@
//
// CustomTableHeader.swift
// LockdownSandbox
//
// Created by Aliaksandr Dvoineu on 24.03.23.
//
import UIKit
enum Section: Int, CaseIterable, CustomStringConvertible {
case lists
case domains
var description: String {
switch self {
case .lists: return "Lists"
case .domains: return "Domains"
}
}
}
class CustomBlockedTableHeader: UITableViewHeaderFooterView {
static let id = "CustomBlockedTableHeader"
private(set) var addButtonCallback: () -> () = { }
private(set) var editButtonCallback: () -> () = { }
lazy var listsTitleLabel: UILabel = {
let label = UILabel()
label.textColor = .label
label.textAlignment = .center
label.font = fontBold18
return label
}()
lazy var addButton: UIButton = {
let button = UIButton(type: .system)
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold, scale: .large)
button.setImage(UIImage(systemName: "plus", withConfiguration: symbolConfig), for: .normal)
button.tintColor = .tunnelsBlue
button.addTarget(self, action: #selector(addButtonDidPress), for: .touchUpInside)
return button
}()
lazy var editButton: UIButton = {
let button = UIButton(type: .system)
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold, scale: .large)
button.setImage(UIImage(named: "icn_edit"), for: .normal)
button.tintColor = .tunnelsBlue
button.addTarget(self, action: #selector(editButtonDidPress), for: .touchUpInside)
button.isHidden = true
return button
}()
var category: Section = .lists {
didSet {
switch category {
case .lists:
listsTitleLabel.text = category.description
case .domains:
listsTitleLabel.text = category.description
}
}
}
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
configure()
}
func configure() {
contentView.addSubview(listsTitleLabel)
contentView.addSubview(addButton)
contentView.addSubview(editButton)
listsTitleLabel.anchors.leading.pin()
listsTitleLabel.anchors.bottom.marginsPin()
addButton.anchors.trailing.pin()
addButton.anchors.bottom.marginsPin()
editButton.anchors.trailing.spacing(12, to: addButton.anchors.leading)
editButton.anchors.bottom.marginsPin()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@discardableResult
func onAddButtonPressed(_ callback: @escaping () -> ()) -> Self {
addButtonCallback = callback
return self
}
@discardableResult
func onEditButtonPressed(_ callback: @escaping () -> ()) -> Self {
editButtonCallback = callback
return self
}
@objc func addButtonDidPress() {
addButtonCallback()
}
@objc func editButtonDidPress() {
editButtonCallback()
}
}

View File

@@ -16,7 +16,7 @@ final class CustomNavigationView: UIView {
}
}
var buttonTitle: String = NSLocalizedString("SAVE", comment: "") {
var buttonTitle: String = NSLocalizedString("CLOSE", comment: "") {
didSet {
button.setTitle(buttonTitle, for: .normal)
}
@@ -45,26 +45,22 @@ final class CustomNavigationView: UIView {
func didLoad() {
addSubview(titleView)
titleView.textAlignment = .left
titleView.textAlignment = .center
titleView.text = title
titleView.font = fontMedium17
titleView.anchors.leading.marginsPin(inset: 20)
titleView.anchors.width.greaterThanOrEqual(220)
titleView.anchors.height.equal(24)
titleView.anchors.centerX.align()
titleView.anchors.top.pin(inset: 18)
addSubview(button)
button.titleLabel?.font = fontBold13
button.contentHorizontalAlignment = .trailing
button.contentHorizontalAlignment = .leading
button.tintColor = .confirmedBlue
button.setTitle(buttonTitle, for: .normal)
button.anchors.centerY.equal(titleView.anchors.centerY)
button.anchors.trailing.marginsPin(inset: 8)
button.anchors.leading.marginsPin(inset: 8)
button.anchors.bottom.marginsPin()
button.anchors.height.equal(39)
button.anchors.width.greaterThanOrEqual(60)
button.addTarget(self, action: #selector(buttonDidPress), for: .touchUpInside)
}

View File

@@ -0,0 +1,94 @@
//
// NothingBlockedView.swift
// Lockdown
//
// Created by Aliaksandr Dvoineu on 21.03.23.
// Copyright © 2023 Confirmed Inc. All rights reserved.
//
import UIKit
final class EmptyListsView: UIView {
var descriptionText: String = "" {
didSet {
descriptionLabel.text = descriptionText
}
}
var buttonTitle: String = "" {
didSet {
addButton.setTitle(buttonTitle, for: .normal)
}
}
private(set) var buttonCallback: () -> () = { }
@discardableResult
func onButtonPressed(_ callback: @escaping () -> ()) -> Self {
buttonCallback = callback
return self
}
// MARK: - Properties
lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.addArrangedSubview(descriptionLabel)
stackView.addArrangedSubview(addButton)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .center
stackView.spacing = 4
return stackView
}()
lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.textColor = .lightGray
label.textAlignment = .center
label.font = fontBold13
label.textAlignment = .center
return label
}()
lazy var addButton: UIButton = {
let button = UIButton(type: .system)
button.tintColor = .tunnelsBlue
button.backgroundColor = .tunnelsLightBlue
button.titleLabel?.font = fontBold13
button.layer.cornerRadius = 8
button.titleEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
return button
}()
// MARK: - Initializer
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Configure UI
func configure() {
addSubview(addButton)
addButton.anchors.width.greaterThanOrEqual(120)
addSubview(stackView)
stackView.anchors.top.marginsPin()
stackView.anchors.bottom.marginsPin()
stackView.anchors.leading.marginsPin()
stackView.anchors.trailing.marginsPin()
}
// - MARK: Functions
@objc func buttonDidPress() {
buttonCallback()
}
}

View File

@@ -0,0 +1,79 @@
//
// ListsSubmenuView.swift
// LockdownSandbox
//
// Created by Aliaksandr Dvoineu on 23.03.23.
//
import UIKit
final class ListsSubmenuView: UIView {
private(set) var buttonCallback: () -> () = { }
@discardableResult
func onButtonPressed(_ callback: @escaping () -> ()) -> Self {
buttonCallback = callback
return self
}
// MARK: - Properties
lazy var createNewListButton: UIButton = {
let button = UIButton(type: .system)
button.tintColor = .tunnelsBlue
button.setTitle("Create New List...", for: .normal)
button.setImage(UIImage(named: "icn_create_list"), for: .normal)
button.setTitleColor(.label, for: .normal)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
return button
}()
lazy var importBlockListButton: UIButton = {
let button = UIButton(type: .system)
button.tintColor = .tunnelsBlue
button.setTitle("Import Block List...", for: .normal)
button.setImage(UIImage(named: "icn_import_list"), for: .normal)
button.setTitleColor(.label, for: .normal)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
return button
}()
lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.addArrangedSubview(createNewListButton)
stackView.addArrangedSubview(importBlockListButton)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .leading
stackView.spacing = 12
return stackView
}()
// MARK: - Initializer
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Functions
func configure() {
backgroundColor = .systemBackground
addSubview(stackView)
stackView.anchors.top.marginsPin()
stackView.anchors.bottom.marginsPin()
stackView.anchors.leading.marginsPin(inset: 10)
stackView.anchors.trailing.marginsPin(inset: 16)
}
@objc func buttonDidPress() {
buttonCallback()
}
}

View File

@@ -10,11 +10,23 @@ import UIKit
final class StaticTableView: UITableView {
// Resizing UITableView to fit content
override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
var rows: [SelectableTableViewCell] = []
var deselectsCellsAutomatically: Bool = false
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
super.init(frame: frame, style: .insetGrouped)
setup()
}
@@ -126,6 +138,10 @@ extension StaticTableView: UITableViewDataSource {
return 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rows.count
}

View File

@@ -145,8 +145,8 @@
/* Class = "UIButton"; normalTitle = "Button"; ObjectID = "O4a-yi-uRp"; */
"O4a-yi-uRp.normalTitle" = "Button";
/* Class = "UITabBarItem"; title = "Protect"; ObjectID = "O5a-jC-Mj1"; */
"O5a-jC-Mj1.title" = "Protect";
/* Class = "UITabBarItem"; title = "Home"; ObjectID = "O5a-jC-Mj1"; */
"O5a-jC-Mj1.title" = "Home";
/* Class = "UILabel"; text = "Location: 🇺🇸"; ObjectID = "O6b-GR-ijA"; */
"O6b-GR-ijA.text" = "Location: 🇺🇸";