Merge branch 'favorite-locations'
This commit is contained in:
commit
2c4e065baf
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## 1.10.0 Beta 2181 (2019-11-21)
|
## 1.10.0 Beta 2181 (2019-11-21)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Favorite provider locations. [#118](https://github.com/passepartoutvpn/passepartout-ios/issues/118)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- "Trusted networks" settings are now saved per profile. [#114](https://github.com/passepartoutvpn/passepartout-ios/issues/114)
|
- "Trusted networks" settings are now saved per profile. [#114](https://github.com/passepartoutvpn/passepartout-ios/issues/114)
|
||||||
|
|
|
@ -69,6 +69,20 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Provider {
|
internal enum Provider {
|
||||||
|
internal enum Pool {
|
||||||
|
internal enum Actions {
|
||||||
|
/// Favorite
|
||||||
|
internal static let favorite = L10n.tr("App", "provider.pool.actions.favorite")
|
||||||
|
/// Unfavorite
|
||||||
|
internal static let unfavorite = L10n.tr("App", "provider.pool.actions.unfavorite")
|
||||||
|
}
|
||||||
|
internal enum Sections {
|
||||||
|
internal enum EmptyFavorites {
|
||||||
|
/// Swipe left on a location to add or remove it from Favorites.
|
||||||
|
internal static let footer = L10n.tr("App", "provider.pool.sections.empty_favorites.footer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
internal enum Preset {
|
internal enum Preset {
|
||||||
internal enum Cells {
|
internal enum Cells {
|
||||||
internal enum TechDetails {
|
internal enum TechDetails {
|
||||||
|
|
|
@ -160,6 +160,22 @@ extension UIActivityIndicatorView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension UIBarButtonItem {
|
||||||
|
func apply(_ theme: Theme) {
|
||||||
|
tintColor = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyAccent(_ theme: Theme) {
|
||||||
|
tintColor = theme.palette.accent1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIContextualAction {
|
||||||
|
func applyNormal(_ theme: Theme) {
|
||||||
|
backgroundColor = theme.palette.primaryBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// XXX: status bar is broken
|
// XXX: status bar is broken
|
||||||
extension MFMailComposeViewController {
|
extension MFMailComposeViewController {
|
||||||
func apply(_ theme: Theme) {
|
func apply(_ theme: Theme) {
|
||||||
|
|
|
@ -48,6 +48,10 @@
|
||||||
"endpoint.sections.location_addresses.header" = "Addresses";
|
"endpoint.sections.location_addresses.header" = "Addresses";
|
||||||
"endpoint.sections.location_protocols.header" = "Protocols";
|
"endpoint.sections.location_protocols.header" = "Protocols";
|
||||||
|
|
||||||
|
"provider.pool.actions.favorite" = "Favorite";
|
||||||
|
"provider.pool.actions.unfavorite" = "Unfavorite";
|
||||||
|
"provider.pool.sections.empty_favorites.footer" = "Swipe left on a location to add or remove it from Favorites.";
|
||||||
|
|
||||||
"provider.preset.cells.tech_details.caption" = "Technical details";
|
"provider.preset.cells.tech_details.caption" = "Technical details";
|
||||||
|
|
||||||
"network_settings.cells.add_dns_server.caption" = "Add address";
|
"network_settings.cells.add_dns_server.caption" = "Add address";
|
||||||
|
|
|
@ -29,28 +29,40 @@ import Convenience
|
||||||
|
|
||||||
protocol ProviderPoolViewControllerDelegate: class {
|
protocol ProviderPoolViewControllerDelegate: class {
|
||||||
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool)
|
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool)
|
||||||
|
|
||||||
|
func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String])
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProviderPoolViewController: UIViewController {
|
class ProviderPoolViewController: UIViewController {
|
||||||
@IBOutlet private weak var tableView: UITableView!
|
@IBOutlet private weak var tableView: UITableView!
|
||||||
|
|
||||||
private var categories: [PoolCategory] = []
|
private var allCategories: [PoolCategory] = []
|
||||||
|
|
||||||
private var sortedGroupsByCategory: [String: [PoolGroup]] = [:]
|
private var favoriteCategories: [PoolCategory] = []
|
||||||
|
|
||||||
private var currentPool: Pool?
|
private var currentPool: Pool?
|
||||||
|
|
||||||
|
private var isShowingFavorites = false
|
||||||
|
|
||||||
|
var favoriteGroupIds: [String] = []
|
||||||
|
|
||||||
|
var isReadonly = false
|
||||||
|
|
||||||
weak var delegate: ProviderPoolViewControllerDelegate?
|
weak var delegate: ProviderPoolViewControllerDelegate?
|
||||||
|
|
||||||
func setInfrastructure(_ infrastructure: Infrastructure, currentPoolId: String?) {
|
func setInfrastructure(_ infrastructure: Infrastructure, currentPoolId: String?) {
|
||||||
categories = infrastructure.categories.sorted { $0.name.lowercased() < $1.name.lowercased() }
|
let sortedCategories = infrastructure.categories.sorted { $0.name.lowercased() < $1.name.lowercased() }
|
||||||
|
allCategories = []
|
||||||
for c in categories {
|
for c in sortedCategories {
|
||||||
sortedGroupsByCategory[c.name] = c.groups.sorted()
|
allCategories.append(PoolCategory(
|
||||||
|
name: c.name,
|
||||||
|
groups: c.groups.sorted(),
|
||||||
|
presets: c.presets
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: uglyyy
|
// XXX: uglyyy
|
||||||
for cat in categories {
|
for cat in allCategories {
|
||||||
for group in cat.groups {
|
for group in cat.groups {
|
||||||
for p in group.pools {
|
for p in group.pools {
|
||||||
if p.id == currentPoolId {
|
if p.id == currentPoolId {
|
||||||
|
@ -72,6 +84,56 @@ class ProviderPoolViewController: UIViewController {
|
||||||
if let ip = selectedIndexPath {
|
if let ip = selectedIndexPath {
|
||||||
tableView.selectRowAsync(at: ip)
|
tableView.selectRowAsync(at: ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: self, action: #selector(toggleFavorites))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Actions
|
||||||
|
|
||||||
|
@objc private func toggleFavorites() {
|
||||||
|
isShowingFavorites = !isShowingFavorites
|
||||||
|
if isShowingFavorites {
|
||||||
|
reloadFavorites()
|
||||||
|
navigationItem.rightBarButtonItem?.applyAccent(.current)
|
||||||
|
} else {
|
||||||
|
navigationItem.rightBarButtonItem?.apply(.current)
|
||||||
|
}
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func favoriteGroup(withId groupId: String) {
|
||||||
|
favoriteGroupIds.append(groupId)
|
||||||
|
delegate?.providerPoolController(self, didUpdateFavoriteGroups: favoriteGroupIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func unfavoriteGroup(in category: PoolCategory, withId groupId: String, deletingRowAt indexPath: IndexPath?) {
|
||||||
|
favoriteGroupIds.removeAll(where: { $0 == groupId })
|
||||||
|
if let indexPath = indexPath {
|
||||||
|
reloadFavorites()
|
||||||
|
if category.groups.count == 1 {
|
||||||
|
tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic)
|
||||||
|
} else {
|
||||||
|
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate?.providerPoolController(self, didUpdateFavoriteGroups: favoriteGroupIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reloadFavorites() {
|
||||||
|
favoriteCategories = []
|
||||||
|
for c in allCategories {
|
||||||
|
let favoriteGroups = c.groups.filter {
|
||||||
|
return favoriteGroupIds.contains($0.uniqueId(in: c))
|
||||||
|
}
|
||||||
|
guard !favoriteGroups.isEmpty else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
favoriteCategories.append(PoolCategory(
|
||||||
|
name: c.name,
|
||||||
|
groups: favoriteGroups,
|
||||||
|
presets: c.presets
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,10 +142,7 @@ class ProviderPoolViewController: UIViewController {
|
||||||
extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate {
|
extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate {
|
||||||
private var selectedIndexPath: IndexPath? {
|
private var selectedIndexPath: IndexPath? {
|
||||||
for (i, cat) in categories.enumerated() {
|
for (i, cat) in categories.enumerated() {
|
||||||
guard let sortedGroups = sortedGroupsByCategory[cat.name] else {
|
for (j, group) in cat.groups.enumerated() {
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (j, group) in sortedGroups.enumerated() {
|
|
||||||
guard let _ = group.pools.firstIndex(where: { $0.id == currentPool?.id }) else {
|
guard let _ = group.pools.firstIndex(where: { $0.id == currentPool?.id }) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -94,18 +153,34 @@ extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
func numberOfSections(in tableView: UITableView) -> Int {
|
func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
|
if isShowingEmptyFavorites {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
return categories.count
|
return categories.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
guard categories.count > 1 else {
|
if isShowingEmptyFavorites {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if categories.count == 1 && categories.first?.name == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let model = categories[section]
|
let model = categories[section]
|
||||||
return model.name
|
return model.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||||
|
if isShowingEmptyFavorites {
|
||||||
|
return L10n.App.Provider.Pool.Sections.EmptyFavorites.footer
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
if isShowingEmptyFavorites {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
let model = categories[section]
|
let model = categories[section]
|
||||||
return model.groups.count
|
return model.groups.count
|
||||||
}
|
}
|
||||||
|
@ -168,11 +243,48 @@ extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate
|
||||||
navigationController?.pushViewController(vc, animated: true)
|
navigationController?.pushViewController(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||||
|
return !isReadonly
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
|
let category = categories[indexPath.section]
|
||||||
|
let group = poolGroup(at: indexPath)
|
||||||
|
let groupId = group.uniqueId(in: category)
|
||||||
|
|
||||||
|
let action: UIContextualAction
|
||||||
|
if favoriteGroupIds.contains(groupId) {
|
||||||
|
action = UIContextualAction(style: .destructive, title: L10n.App.Provider.Pool.Actions.unfavorite) {
|
||||||
|
self.unfavoriteGroup(in: category, withId: groupId, deletingRowAt: self.isShowingFavorites ? indexPath : nil)
|
||||||
|
$2(true)
|
||||||
|
}
|
||||||
|
} else if !isShowingFavorites {
|
||||||
|
action = UIContextualAction(style: .normal, title: L10n.App.Provider.Pool.Actions.favorite) {
|
||||||
|
self.favoriteGroup(withId: groupId)
|
||||||
|
$2(true)
|
||||||
|
}
|
||||||
|
action.applyNormal(.current)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let cfg = UISwipeActionsConfiguration(actions: [action])
|
||||||
|
cfg.performsFirstActionWithFullSwipe = false
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Helpers
|
||||||
|
|
||||||
private func poolGroup(at indexPath: IndexPath) -> PoolGroup {
|
private func poolGroup(at indexPath: IndexPath) -> PoolGroup {
|
||||||
let model = categories[indexPath.section]
|
let model = categories[indexPath.section]
|
||||||
guard let sortedGroups = sortedGroupsByCategory[model.name] else {
|
return model.groups[indexPath.row]
|
||||||
fatalError("Missing sorted groups for category '\(model.name)'")
|
}
|
||||||
}
|
|
||||||
return sortedGroups[indexPath.row]
|
private var categories: [PoolCategory] {
|
||||||
|
return isShowingFavorites ? favoriteCategories : allCategories
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isShowingEmptyFavorites: Bool {
|
||||||
|
return isShowingFavorites && favoriteGroupIds.isEmpty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,6 +162,7 @@ class ServiceViewController: UIViewController, StrongTableHost {
|
||||||
case .providerPoolSegueIdentifier:
|
case .providerPoolSegueIdentifier:
|
||||||
let vc = destination as? ProviderPoolViewController
|
let vc = destination as? ProviderPoolViewController
|
||||||
vc?.setInfrastructure(uncheckedProviderProfile.infrastructure, currentPoolId: uncheckedProviderProfile.poolId)
|
vc?.setInfrastructure(uncheckedProviderProfile.infrastructure, currentPoolId: uncheckedProviderProfile.poolId)
|
||||||
|
vc?.favoriteGroupIds = uncheckedProviderProfile.favoriteGroupIds ?? []
|
||||||
vc?.delegate = self
|
vc?.delegate = self
|
||||||
|
|
||||||
case .endpointSegueIdentifier:
|
case .endpointSegueIdentifier:
|
||||||
|
@ -1425,6 +1426,10 @@ extension ServiceViewController: ProviderPoolViewControllerDelegate {
|
||||||
IntentDispatcher.donateConnection(with: uncheckedProviderProfile)
|
IntentDispatcher.donateConnection(with: uncheckedProviderProfile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String]) {
|
||||||
|
uncheckedProviderProfile.favoriteGroupIds = favoriteGroupIds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ServiceViewController: ProviderPresetViewControllerDelegate {
|
extension ServiceViewController: ProviderPresetViewControllerDelegate {
|
||||||
|
|
|
@ -173,4 +173,7 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
|
||||||
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool) {
|
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool) {
|
||||||
addMoveToLocation(pool: pool)
|
addMoveToLocation(pool: pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String]) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 2b6edafb0cd34c331aa0a7040571ef82a552ad97
|
Subproject commit 79cc4a739978b6fc98e910c65d7913431cb41915
|
Loading…
Reference in New Issue