Commit d03d6d52 authored by Jose Blaya's avatar Jose Blaya
Browse files

Merge branch '224-piax-region-selection-no-results-view' into...

Merge branch '224-piax-region-selection-no-results-view' into '223-piax-region-selection-add-search-bar'

Resolve "PIAX. Region Selection. No results view"

See merge request ios/vpn-ios!342
parents 531f4e22 44631092
This diff is collapsed.
......@@ -14,6 +14,7 @@ import SwiftyBeaver
private let log = SwiftyBeaver.self
class AppPreferences {
private struct Entries {
static let version = "Version"
......@@ -28,6 +29,10 @@ class AppPreferences {
static let lastVPNConnectionStatus = "LastVPNConnectionStatus"
static let piaSocketType = "PIASocketType"
static let favoriteServerIdentifiers = "FavoriteServerIdentifiers"
static let regionFilter = "RegionFilter"
}
static let shared = AppPreferences()
......@@ -104,6 +109,30 @@ class AppPreferences {
}
}
var favoriteServerIdentifiers: [String] {
get {
if let serverIdentifiers = defaults.array(forKey: Entries.favoriteServerIdentifiers) as? [String] {
return serverIdentifiers
}
return []
}
set {
defaults.set(newValue, forKey: Entries.favoriteServerIdentifiers)
}
}
var regionFilter: RegionFilter {
get {
guard let rawValue = defaults.string(forKey: Entries.regionFilter) else {
return .name
}
return RegionFilter(rawValue: rawValue) ?? .name
}
set {
defaults.set(newValue.rawValue, forKey: Entries.regionFilter)
}
}
private init() {
guard let defaults = UserDefaults(suiteName: AppConstants.appGroup) else {
fatalError("Unable to initialize app preferences")
......@@ -113,6 +142,8 @@ class AppPreferences {
defaults.register(defaults: [
Entries.version: AppPreferences.currentVersion,
Entries.launched: false,
Entries.regionFilter: RegionFilter.name.rawValue,
Entries.favoriteServerIdentifiers: [],
Entries.didAskToEnableNotifications: false,
Entries.themeCode: ThemeCode.light.rawValue
])
......
{
"images" : [
{
"idiom" : "universal",
"filename" : "group92.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "15IllustrationNoResultDarkCopy2.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "15IllustrationNoResultLightCopy2.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
//
// PropertyStoring.swift
// PIA VPN
//
// Created by Jose Antonio Blaya Garcia on 28/12/2018.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import Foundation
protocol PropertyStoring {
associatedtype T
func getAssociatedObject(_ key: UnsafeRawPointer!, defaultValue: T) -> T
}
extension PropertyStoring {
func getAssociatedObject(_ key: UnsafeRawPointer!, defaultValue: T) -> T {
guard let value = objc_getAssociatedObject(self, key) as? T else {
return defaultValue
}
return value
}
}
......@@ -22,12 +22,14 @@ class RegionCell: UITableViewCell, Restylable {
@IBOutlet private weak var selectedRegionImageView: UIImageView!
private var isFavorite: Bool!
private weak var server: Server!
override func setSelected(_ selected: Bool, animated: Bool) {
}
func fill(withServer server: Server, isSelected: Bool) {
viewShouldRestyle()
self.server = server
imvFlag.setImage(fromServer: server)
labelRegion.text = server.name
......@@ -50,10 +52,10 @@ class RegionCell: UITableViewCell, Restylable {
accessibilityIdentifier = "uitests.regions.region_name"
self.favoriteImageView.image = self.favoriteImageView.image?.withRenderingMode(.alwaysTemplate)
self.favoriteImageView.alpha = CGFloat(NSNumber(booleanLiteral: server.name != L10n.Global.automatic).floatValue)
self.favoriteButton.alpha = CGFloat(NSNumber(booleanLiteral: server.name != L10n.Global.automatic).floatValue)
self.favoriteImageView.alpha = CGFloat(NSNumber(booleanLiteral: !server.isAutomatic).floatValue)
self.favoriteButton.alpha = CGFloat(NSNumber(booleanLiteral: !server.isAutomatic).floatValue)
self.isFavorite = isSelected
self.isFavorite = server.isFavorite
self.updateFavoriteImage()
self.setSelected(false, animated: false)
......@@ -78,6 +80,7 @@ class RegionCell: UITableViewCell, Restylable {
@IBAction func favoriteServer(_ sender: UIButton) {
self.isFavorite = !self.isFavorite
self.isFavorite ? self.server.favorite() : self.server.unfavorite()
self.animateFavoriteImage()
}
......
//
// RegionFilter.swift
// PIA VPN
//
// Created by Jose Antonio Blaya Garcia on 28/12/2018.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import Foundation
public enum RegionFilter: String {
case name
case latency
case favorite
}
......@@ -8,6 +8,8 @@
import UIKit
import PIALibrary
import DZNEmptyDataSet
import PopupDialog
class RegionsViewController: AutolayoutViewController {
private struct Cells {
......@@ -32,7 +34,14 @@ class RegionsViewController: AutolayoutViewController {
title = L10n.Menu.Item.region
var servers = Client.providers.serverProvider.currentServers
servers.insert(Server.automatic, at: 0)
let favoriteServers = AppPreferences.shared.favoriteServerIdentifiers
for server in servers {
server.isFavorite = favoriteServers.contains(server.identifier)
}
self.servers = servers
filterServers()
selectedServer = Client.preferences.displayedServer
......@@ -40,6 +49,43 @@ class RegionsViewController: AutolayoutViewController {
NotificationCenter.default.addObserver(self, selector: #selector(viewHasRotated), name: .UIDeviceOrientationDidChange, object: nil)
setupSearchBarController()
stylePopupDialog()
tableView.emptyDataSetSource = self
tableView.emptyDataSetDelegate = self
}
private func setupRightBarButton() {
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: Asset.Piax.Global.iconFilter.image,
style: .plain,
target: self,
action: #selector(showFilter(_:))
)
navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Region.Accessibility.filter
}
private func stylePopupDialog() {
let dialogAppearance = PopupDialogDefaultView.appearance()
dialogAppearance.backgroundColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white
dialogAppearance.titleFont = TextStyle.textStyle12.font!
dialogAppearance.titleColor = Theme.current.palette.appearance == .dark ? .white : TextStyle.textStyle12.color
let containerAppearance = PopupDialogContainerView.appearance()
containerAppearance.cornerRadius = 0
containerAppearance.shadowEnabled = false
let overlayAppearance = PopupDialogOverlayView.appearance()
overlayAppearance.color = .black
overlayAppearance.blurEnabled = false
overlayAppearance.liveBlurEnabled = false
overlayAppearance.opacity = 0.5
let buttonAppearance = DefaultButton.appearance()
buttonAppearance.titleFont = TextStyle.textStyle21.font!
buttonAppearance.titleColor = TextStyle.textStyle21.color
buttonAppearance.buttonColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey6 : .white
buttonAppearance.separatorColor = Theme.current.palette.appearance == .dark ? UIColor.piaGrey10 : UIColor.piaGrey1
}
private func setupSearchBarController() {
......@@ -54,8 +100,10 @@ class RegionsViewController: AutolayoutViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
styleNavigationBarWithTitle(L10n.Menu.Item.region)
setupRightBarButton()
let selectedRow = servers.index { (server) -> Bool in
return (server.identifier == selectedServer.identifier)
}
......@@ -67,6 +115,53 @@ class RegionsViewController: AutolayoutViewController {
}
// MARK: Actions
@objc private func showFilter(_ sender: Any?) {
let popup = PopupDialog(title: L10n.Region.Filter.sortby.uppercased(),
message: nil)
let buttonName = DefaultButton(title: L10n.Region.Filter.name.uppercased(), dismissOnTap: true) {
AppPreferences.shared.regionFilter = .name
self.filterServers()
}
let buttonLatency = DefaultButton(title: L10n.Region.Filter.latency.uppercased(), dismissOnTap: true) {
AppPreferences.shared.regionFilter = .latency
self.filterServers()
}
let buttonFavorites = DefaultButton(title: L10n.Region.Filter.favorites.uppercased(), dismissOnTap: true) {
AppPreferences.shared.regionFilter = .favorite
self.filterServers()
}
switch AppPreferences.shared.regionFilter {
case .name:
buttonName.titleColor = UIColor.piaGreenDark20
case .latency:
buttonLatency.titleColor = UIColor.piaGreenDark20
default:
buttonFavorites.titleColor = UIColor.piaGreenDark20
}
popup.addButtons([buttonName, buttonLatency, buttonFavorites])
self.present(popup, animated: true, completion: nil)
}
private func filterServers() {
self.servers = servers.filter({ !$0.isAutomatic })
switch AppPreferences.shared.regionFilter {
case .name:
self.servers = self.servers.sorted(by: { $0.name < $1.name })
case .latency:
self.servers = self.servers.sorted(by: { $0.pingTime ?? 0 < $1.pingTime ?? 0 })
default:
self.servers = self.servers.sorted(by: { $0.isFavorite && !$1.isFavorite })
}
tableView.reloadData()
self.servers.insert(Server.automatic, at: 0)
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
@objc private func viewHasRotated() {
styleNavigationBarWithTitle(L10n.Menu.Item.region)
}
......@@ -202,3 +297,22 @@ extension RegionsViewController: UISearchResultsUpdating {
return searchController.isActive && !searchBarIsEmpty()
}
}
extension RegionsViewController: DZNEmptyDataSetSource, DZNEmptyDataSetDelegate {
func image(forEmptyDataSet scrollView: UIScrollView!) -> UIImage! {
return Theme.current.noResultsImage()
}
func emptyDataSetWillAppear(_ scrollView: UIScrollView!) {
tableView.separatorStyle = .none
}
func emptyDataSetWillDisappear(_ scrollView: UIScrollView!) {
tableView.separatorStyle = .singleLine
}
func emptyDataSet(_ scrollView: UIScrollView!, didTap view: UIView!) {
searchController.searchBar.resignFirstResponder()
}
}
//
// Server+Favorite.swift
// PIA VPN
//
// Created by Jose Antonio Blaya Garcia on 28/12/2018.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import Foundation
import PIALibrary
protocol Favoritable {
/// Favorite this server and update the cached servers
func favorite()
/// Unfavorite this server and update the cached servers
func unfavorite()
}
extension Server: Favoritable, PropertyStoring {
typealias T = Bool
private struct CustomProperties {
static var isFavorite = false
}
var isFavorite: Bool {
get {
return getAssociatedObject(&CustomProperties.isFavorite, defaultValue: CustomProperties.isFavorite)
}
set {
return objc_setAssociatedObject(self, &CustomProperties.isFavorite, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func favorite() {
self.isFavorite = true
var currentFavorites = AppPreferences.shared.favoriteServerIdentifiers
currentFavorites.append(self.identifier)
AppPreferences.shared.favoriteServerIdentifiers = currentFavorites
}
func unfavorite() {
self.isFavorite = false
let currentFavorites = AppPreferences.shared.favoriteServerIdentifiers
let filteredFavorites = currentFavorites.filter({$0 != self.identifier})
AppPreferences.shared.favoriteServerIdentifiers = filteredFavorites
}
}
......@@ -53,12 +53,17 @@ enum Asset {
static let favoriteUnselectedDark = ImageAsset(name: "favorite-unselected-dark")
static let favoriteUnselected = ImageAsset(name: "favorite-unselected")
static let iconBack = ImageAsset(name: "icon-back")
static let iconFilter = ImageAsset(name: "icon-filter")
static let pagecontrolSelectedDot = ImageAsset(name: "pagecontrol-selected-dot")
static let pagecontrolUnselectedDot = ImageAsset(name: "pagecontrol-unselected-dot")
static let regionSelected = ImageAsset(name: "region-selected")
static let scrollableMapDark = ImageAsset(name: "scrollableMap-dark")
static let scrollableMapLight = ImageAsset(name: "scrollableMap-light")
}
enum Regions {
static let noResultsDark = ImageAsset(name: "no-results-dark")
static let noResultsLight = ImageAsset(name: "no-results-light")
}
enum Splash {
static let darkSplash = ImageAsset(name: "dark-splash")
static let lightSplash = ImageAsset(name: "light-splash")
......@@ -327,11 +332,14 @@ enum Asset {
Piax.Global.favoriteUnselectedDark,
Piax.Global.favoriteUnselected,
Piax.Global.iconBack,
Piax.Global.iconFilter,
Piax.Global.pagecontrolSelectedDot,
Piax.Global.pagecontrolUnselectedDot,
Piax.Global.regionSelected,
Piax.Global.scrollableMapDark,
Piax.Global.scrollableMapLight,
Piax.Regions.noResultsDark,
Piax.Regions.noResultsLight,
Piax.Splash.darkSplash,
Piax.Splash.lightSplash,
accessoryExpire,
......
......@@ -279,6 +279,20 @@ internal enum L10n {
}
internal enum Region {
internal enum Accessibility {
/// Filter
internal static let filter = L10n.tr("Localizable", "region.accessibility.filter")
}
internal enum Filter {
/// Favorites
internal static let favorites = L10n.tr("Localizable", "region.filter.favorites")
/// Latency
internal static let latency = L10n.tr("Localizable", "region.filter.latency")
/// Name
internal static let name = L10n.tr("Localizable", "region.filter.name")
/// Sort regions by
internal static let sortby = L10n.tr("Localizable", "region.filter.sortby")
}
internal enum Search {
/// Search for a region
internal static let placeholder = L10n.tr("Localizable", "region.search.placeholder")
......
......@@ -154,4 +154,10 @@ extension Theme {
}
}
public func noResultsImage() -> UIImage {
return palette.appearance == .dark ?
Asset.Piax.Regions.noResultsDark.image :
Asset.Piax.Regions.noResultsLight.image
}
}
......@@ -202,3 +202,8 @@
// REGION
"region.search.placeholder" = "Search for a region";
"region.accessibility.filter" = "Filter";
"region.filter.sortby" = "Sort regions by";
"region.filter.name" = "Name";
"region.filter.latency" = "Latency";
"region.filter.favorites" = "Favorites";
......@@ -64,6 +64,8 @@ def app_pods
pod 'SideMenu', '= 3.1.5'
pod 'FXPageControl'
pod 'MBProgressHUD'
pod 'DZNEmptyDataSet'
pod 'PopupDialog'
end
def tunnel_pods
......@@ -89,3 +91,13 @@ end
target 'PIA VPN Tunnel' do
tunnel_pods
end
post_install do |installer|
installer.pods_project.targets.each do |target|
if ['PopupDialog'].include? target.name
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '4.2'
end
end
end
end
......@@ -4,6 +4,8 @@ PODS:
- Alamofire (~> 4.7)
- Crashlytics (3.11.1):
- Fabric (~> 1.8.1)
- DynamicBlurView (3.0.1)
- DZNEmptyDataSet (1.8.1)
- Fabric (1.8.1)
- Firebase/Core (5.11.0):
- Firebase/CoreOnly
......@@ -93,6 +95,8 @@ PODS:
- PIATunnel/Core (1.1.8):
- OpenSSL-Apple (~> 1.1.0h)
- SwiftyBeaver
- PopupDialog (0.9.1):
- DynamicBlurView (~> 3.0.1)
- QuickLayout (2.0.2)
- ReachabilitySwift (4.3.0)
- SideMenu (3.1.5)
......@@ -104,6 +108,7 @@ PODS:
DEPENDENCIES:
- AlamofireImage
- Crashlytics
- DZNEmptyDataSet
- Fabric
- Firebase/Core
- FXPageControl
......@@ -115,6 +120,7 @@ DEPENDENCIES:
- PIALibrary/UI (from `/Users/ueshiba/Desktop/PIA/client-library-apple`)
- PIALibrary/VPN (from `/Users/ueshiba/Desktop/PIA/client-library-apple`)
- PIATunnel (from `/Users/ueshiba/Desktop/PIA/tunnel-apple`)
- PopupDialog
- SideMenu (= 3.1.5)
- TPKeyboardAvoiding
......@@ -123,6 +129,8 @@ SPEC REPOS:
- Alamofire
- AlamofireImage
- Crashlytics
- DynamicBlurView
- DZNEmptyDataSet
- Fabric
- Firebase
- FirebaseAnalytics
......@@ -138,6 +146,7 @@ SPEC REPOS:
- MBProgressHUD
- nanopb
- OpenSSL-Apple
- PopupDialog
- QuickLayout
- ReachabilitySwift
- SideMenu
......@@ -155,6 +164,8 @@ SPEC CHECKSUMS:
Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568
AlamofireImage: 78d67ccbb763d87ba44b21583d2153500a195630
Crashlytics: ca7ab4bc304aa216bdc2e4c1a96389ee77252203
DynamicBlurView: b1df5415f9bd31897549e5d7077e5ec120a4d636
DZNEmptyDataSet: 9525833b9e68ac21c30253e1d3d7076cc828eaa7
Fabric: 43ed7bac010f588f63ee23ab1a0276b296db5e17
Firebase: b48f9e653da971ecce5b8c749684bc8bb2d26bd3
FirebaseAnalytics: 63202d2665de4e6adcbdce189135255d8b5962ba
......@@ -172,6 +183,7 @@ SPEC CHECKSUMS:
OpenSSL-Apple: cd153d705ef350eb834ae7ff5f21f792b51ed208
PIALibrary: 31ba3f0a1575931be2472d1b9d590f08bd7876be
PIATunnel: fc7e7d0a812c6c98138ac315e7a43008aadd15b2
PopupDialog: 03985b669c29802661c187513bf827340db1ae4d
QuickLayout: a730730b646b231fd4ef7cffaeb1e81fe0e1ca92
ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3
SideMenu: 47dbf9e4d878062d8994aed43f6e4bf6c1fea30b
......@@ -179,6 +191,6 @@ SPEC CHECKSUMS:
SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a
TPKeyboardAvoiding: cb69d5ddbe90ce0170e4bc2db1e5e41d4a3ad9a4
PODFILE CHECKSUM: 3e857e96f910e963b7a2a6ed0f4b0968875616f1
PODFILE CHECKSUM: 87fc45c9dcad7b255ea49de23cf784af7b7efa83
COCOAPODS: 1.5.3
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment