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)
|
||||
|
||||
### Added
|
||||
|
||||
- Favorite provider locations. [#118](https://github.com/passepartoutvpn/passepartout-ios/issues/118)
|
||||
|
||||
### Changed
|
||||
|
||||
- "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 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 Cells {
|
||||
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
|
||||
extension MFMailComposeViewController {
|
||||
func apply(_ theme: Theme) {
|
||||
|
|
|
@ -48,6 +48,10 @@
|
|||
"endpoint.sections.location_addresses.header" = "Addresses";
|
||||
"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";
|
||||
|
||||
"network_settings.cells.add_dns_server.caption" = "Add address";
|
||||
|
|
|
@ -29,28 +29,40 @@ import Convenience
|
|||
|
||||
protocol ProviderPoolViewControllerDelegate: class {
|
||||
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool)
|
||||
|
||||
func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String])
|
||||
}
|
||||
|
||||
class ProviderPoolViewController: UIViewController {
|
||||
@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 isShowingFavorites = false
|
||||
|
||||
var favoriteGroupIds: [String] = []
|
||||
|
||||
var isReadonly = false
|
||||
|
||||
weak var delegate: ProviderPoolViewControllerDelegate?
|
||||
|
||||
func setInfrastructure(_ infrastructure: Infrastructure, currentPoolId: String?) {
|
||||
categories = infrastructure.categories.sorted { $0.name.lowercased() < $1.name.lowercased() }
|
||||
|
||||
for c in categories {
|
||||
sortedGroupsByCategory[c.name] = c.groups.sorted()
|
||||
let sortedCategories = infrastructure.categories.sorted { $0.name.lowercased() < $1.name.lowercased() }
|
||||
allCategories = []
|
||||
for c in sortedCategories {
|
||||
allCategories.append(PoolCategory(
|
||||
name: c.name,
|
||||
groups: c.groups.sorted(),
|
||||
presets: c.presets
|
||||
))
|
||||
}
|
||||
|
||||
// XXX: uglyyy
|
||||
for cat in categories {
|
||||
for cat in allCategories {
|
||||
for group in cat.groups {
|
||||
for p in group.pools {
|
||||
if p.id == currentPoolId {
|
||||
|
@ -72,6 +84,56 @@ class ProviderPoolViewController: UIViewController {
|
|||
if let ip = selectedIndexPath {
|
||||
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 {
|
||||
private var selectedIndexPath: IndexPath? {
|
||||
for (i, cat) in categories.enumerated() {
|
||||
guard let sortedGroups = sortedGroupsByCategory[cat.name] else {
|
||||
continue
|
||||
}
|
||||
for (j, group) in sortedGroups.enumerated() {
|
||||
for (j, group) in cat.groups.enumerated() {
|
||||
guard let _ = group.pools.firstIndex(where: { $0.id == currentPool?.id }) else {
|
||||
continue
|
||||
}
|
||||
|
@ -94,18 +153,34 @@ extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate
|
|||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
if isShowingEmptyFavorites {
|
||||
return 1
|
||||
}
|
||||
return categories.count
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
let model = categories[section]
|
||||
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 {
|
||||
if isShowingEmptyFavorites {
|
||||
return 0
|
||||
}
|
||||
let model = categories[section]
|
||||
return model.groups.count
|
||||
}
|
||||
|
@ -168,11 +243,48 @@ extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate
|
|||
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 {
|
||||
let model = categories[indexPath.section]
|
||||
guard let sortedGroups = sortedGroupsByCategory[model.name] else {
|
||||
fatalError("Missing sorted groups for category '\(model.name)'")
|
||||
}
|
||||
return sortedGroups[indexPath.row]
|
||||
return model.groups[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:
|
||||
let vc = destination as? ProviderPoolViewController
|
||||
vc?.setInfrastructure(uncheckedProviderProfile.infrastructure, currentPoolId: uncheckedProviderProfile.poolId)
|
||||
vc?.favoriteGroupIds = uncheckedProviderProfile.favoriteGroupIds ?? []
|
||||
vc?.delegate = self
|
||||
|
||||
case .endpointSegueIdentifier:
|
||||
|
@ -1425,6 +1426,10 @@ extension ServiceViewController: ProviderPoolViewControllerDelegate {
|
|||
IntentDispatcher.donateConnection(with: uncheckedProviderProfile)
|
||||
}
|
||||
}
|
||||
|
||||
func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String]) {
|
||||
uncheckedProviderProfile.favoriteGroupIds = favoriteGroupIds
|
||||
}
|
||||
}
|
||||
|
||||
extension ServiceViewController: ProviderPresetViewControllerDelegate {
|
||||
|
|
|
@ -173,4 +173,7 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
|
|||
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool) {
|
||||
addMoveToLocation(pool: pool)
|
||||
}
|
||||
|
||||
func providerPoolController(_: ProviderPoolViewController, didUpdateFavoriteGroups favoriteGroupIds: [String]) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2b6edafb0cd34c331aa0a7040571ef82a552ad97
|
||||
Subproject commit 79cc4a739978b6fc98e910c65d7913431cb41915
|
Loading…
Reference in New Issue