mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-01-18 22:49:10 +00:00
Add "Refresh infrastructure" in server lists (#938)
Refactoring: - Split Providers and VPN views - Rename VPNProviderServerView subviews - Reuse RefreshInfrastructureButton Closes #929
This commit is contained in:
parent
8b043d8a4f
commit
b357d985ed
@ -27,6 +27,7 @@ import CommonAPI
|
|||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UILibrary
|
||||||
|
|
||||||
struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity: ProviderEntity, Entity.Configuration: ProviderConfigurationIdentifiable & Codable, ProviderRows: View {
|
struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity: ProviderEntity, Entity.Configuration: ProviderConfigurationIdentifiable & Codable, ProviderRows: View {
|
||||||
|
|
||||||
@ -77,18 +78,10 @@ private extension ProviderContentModifier {
|
|||||||
providerPicker
|
providerPicker
|
||||||
.themeSection()
|
.themeSection()
|
||||||
|
|
||||||
if providerId != nil {
|
if let providerId {
|
||||||
Group {
|
Group {
|
||||||
providerRows
|
providerRows
|
||||||
refreshButton {
|
RefreshInfrastructureButton(apis: apis, providerId: providerId)
|
||||||
HStack {
|
|
||||||
Text(Strings.Views.Providers.refreshInfrastructure)
|
|
||||||
if providerManager.isLoading {
|
|
||||||
Spacer()
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.themeSection(footer: lastUpdatedString)
|
.themeSection(footer: lastUpdatedString)
|
||||||
}
|
}
|
||||||
@ -99,7 +92,7 @@ private extension ProviderContentModifier {
|
|||||||
Section {
|
Section {
|
||||||
providerPicker
|
providerPicker
|
||||||
}
|
}
|
||||||
if providerId != nil {
|
if let providerId {
|
||||||
Section {
|
Section {
|
||||||
providerRows
|
providerRows
|
||||||
HStack {
|
HStack {
|
||||||
@ -108,9 +101,7 @@ private extension ProviderContentModifier {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
refreshButton {
|
RefreshInfrastructureButton(apis: apis, providerId: providerId)
|
||||||
Text(Strings.Views.Providers.refreshInfrastructure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,10 +118,6 @@ private extension ProviderContentModifier {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshButton<Label>(label: () -> Label) -> some View where Label: View {
|
|
||||||
Button(action: onRefreshInfrastructure, label: label)
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedProviders: [ProviderMetadata] {
|
var supportedProviders: [ProviderMetadata] {
|
||||||
providerManager
|
providerManager
|
||||||
.providers
|
.providers
|
||||||
@ -185,15 +172,6 @@ private extension ProviderContentModifier {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func onRefreshInfrastructure() {
|
|
||||||
guard let providerId else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Task {
|
|
||||||
await refreshInfrastructure(for: providerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Preview
|
// MARK: - Preview
|
||||||
|
@ -29,6 +29,9 @@ import PassepartoutKit
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct VPNFiltersView: View {
|
struct VPNFiltersView: View {
|
||||||
|
let apis: [APIMapper]
|
||||||
|
|
||||||
|
let providerId: ProviderID
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var model: Model
|
var model: Model
|
||||||
@ -47,6 +50,7 @@ struct VPNFiltersView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
favoritesToggle
|
favoritesToggle
|
||||||
Spacer()
|
Spacer()
|
||||||
|
RefreshInfrastructureButton(apis: apis, providerId: providerId)
|
||||||
clearFiltersButton
|
clearFiltersButton
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -88,7 +92,7 @@ private extension VPNFiltersView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var presetPicker: some View {
|
var presetPicker: some View {
|
||||||
Picker(Strings.Views.Providers.Vpn.preset, selection: $model.filters.presetId) {
|
Picker(Strings.Views.Vpn.preset, selection: $model.filters.presetId) {
|
||||||
Text(Strings.Global.Nouns.any)
|
Text(Strings.Global.Nouns.any)
|
||||||
.tag(nil as String?)
|
.tag(nil as String?)
|
||||||
ForEach(model.presets, id: \.presetId) {
|
ForEach(model.presets, id: \.presetId) {
|
||||||
@ -111,6 +115,10 @@ private extension VPNFiltersView {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
VPNFiltersView(model: .init())
|
VPNFiltersView(
|
||||||
|
apis: [API.bundled],
|
||||||
|
providerId: .mullvad,
|
||||||
|
model: .init()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -71,6 +71,8 @@ extension VPNProviderServerView {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
debugChanges()
|
debugChanges()
|
||||||
return ContentView(
|
return ContentView(
|
||||||
|
apis: apis,
|
||||||
|
providerId: providerId,
|
||||||
servers: filteredServers,
|
servers: filteredServers,
|
||||||
selectedServer: selectedServer,
|
selectedServer: selectedServer,
|
||||||
isFiltering: isFiltering,
|
isFiltering: isFiltering,
|
@ -84,7 +84,11 @@ extension VPNProviderServerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var filtersView: some View {
|
var filtersView: some View {
|
||||||
VPNFiltersView(model: filtersViewModel)
|
VPNFiltersView(
|
||||||
|
apis: apis,
|
||||||
|
providerId: providerId,
|
||||||
|
model: filtersViewModel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var initialFilters: VPNFilters? {
|
var initialFilters: VPNFilters? {
|
@ -32,6 +32,10 @@ import SwiftUI
|
|||||||
|
|
||||||
extension VPNProviderServerView {
|
extension VPNProviderServerView {
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
let apis: [APIMapper]
|
||||||
|
|
||||||
|
let providerId: ProviderID
|
||||||
|
|
||||||
let servers: [VPNServer]
|
let servers: [VPNServer]
|
||||||
|
|
||||||
let selectedServer: VPNServer?
|
let selectedServer: VPNServer?
|
||||||
@ -68,6 +72,7 @@ private extension VPNProviderServerView.ContentView {
|
|||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
Toggle(Strings.Views.Providers.onlyFavorites, isOn: $filtersViewModel.onlyShowsFavorites)
|
Toggle(Strings.Views.Providers.onlyFavorites, isOn: $filtersViewModel.onlyShowsFavorites)
|
||||||
|
RefreshInfrastructureButton(apis: apis, providerId: providerId)
|
||||||
}
|
}
|
||||||
Group {
|
Group {
|
||||||
if isFiltering || !servers.isEmpty {
|
if isFiltering || !servers.isEmpty {
|
||||||
@ -82,7 +87,7 @@ private extension VPNProviderServerView.ContentView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.themeSection(
|
.themeSection(
|
||||||
header: filtersViewModel.filters.categoryName ?? Strings.Views.Providers.Vpn.Category.any
|
header: filtersViewModel.filters.categoryName ?? Strings.Views.Vpn.Category.any
|
||||||
)
|
)
|
||||||
.onLoad {
|
.onLoad {
|
||||||
if let selectedServer = selectedServer {
|
if let selectedServer = selectedServer {
|
||||||
@ -93,7 +98,7 @@ private extension VPNProviderServerView.ContentView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var emptyView: some View {
|
var emptyView: some View {
|
||||||
Text(Strings.Views.Providers.Vpn.noServers)
|
Text(Strings.Views.Vpn.noServers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,8 +165,10 @@ private extension VPNProviderServerView.ContentView {
|
|||||||
list.append($0)
|
list.append($0)
|
||||||
map[code] = list
|
map[code] = list
|
||||||
}
|
}
|
||||||
|
withAnimation {
|
||||||
serversByCountryCode = map
|
serversByCountryCode = map
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -36,6 +36,10 @@ extension VPNProviderServerView {
|
|||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
||||||
|
let apis: [APIMapper]
|
||||||
|
|
||||||
|
let providerId: ProviderID
|
||||||
|
|
||||||
let servers: [VPNServer]
|
let servers: [VPNServer]
|
||||||
|
|
||||||
let selectedServer: VPNServer?
|
let selectedServer: VPNServer?
|
@ -26,7 +26,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
public func debugChanges(condition: Bool = true) {
|
public func debugChanges(condition: Bool = false) {
|
||||||
if condition {
|
if condition {
|
||||||
Self._printChanges()
|
Self._printChanges()
|
||||||
}
|
}
|
||||||
|
@ -830,16 +830,6 @@ public enum Strings {
|
|||||||
/// Loading...
|
/// Loading...
|
||||||
public static let loading = Strings.tr("Localizable", "views.providers.last_updated.loading", fallback: "Loading...")
|
public static let loading = Strings.tr("Localizable", "views.providers.last_updated.loading", fallback: "Loading...")
|
||||||
}
|
}
|
||||||
public enum Vpn {
|
|
||||||
/// No servers
|
|
||||||
public static let noServers = Strings.tr("Localizable", "views.providers.vpn.no_servers", fallback: "No servers")
|
|
||||||
/// Preset
|
|
||||||
public static let preset = Strings.tr("Localizable", "views.providers.vpn.preset", fallback: "Preset")
|
|
||||||
public enum Category {
|
|
||||||
/// All categories
|
|
||||||
public static let any = Strings.tr("Localizable", "views.providers.vpn.category.any", fallback: "All categories")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public enum Purchased {
|
public enum Purchased {
|
||||||
/// No purchases
|
/// No purchases
|
||||||
@ -883,6 +873,16 @@ public enum Strings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public enum Vpn {
|
||||||
|
/// No servers
|
||||||
|
public static let noServers = Strings.tr("Localizable", "views.vpn.no_servers", fallback: "No servers")
|
||||||
|
/// Preset
|
||||||
|
public static let preset = Strings.tr("Localizable", "views.vpn.preset", fallback: "Preset")
|
||||||
|
public enum Category {
|
||||||
|
/// All categories
|
||||||
|
public static let any = Strings.tr("Localizable", "views.vpn.category.any", fallback: "All categories")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||||
|
@ -115,9 +115,6 @@
|
|||||||
"views.providers.refresh_infrastructure" = "Refresh infrastructure";
|
"views.providers.refresh_infrastructure" = "Refresh infrastructure";
|
||||||
"views.providers.last_updated" = "Last updated on %@";
|
"views.providers.last_updated" = "Last updated on %@";
|
||||||
"views.providers.last_updated.loading" = "Loading...";
|
"views.providers.last_updated.loading" = "Loading...";
|
||||||
"views.providers.vpn.category.any" = "All categories";
|
|
||||||
"views.providers.vpn.preset" = "Preset";
|
|
||||||
"views.providers.vpn.no_servers" = "No servers";
|
|
||||||
|
|
||||||
"views.purchased.title" = "Purchased";
|
"views.purchased.title" = "Purchased";
|
||||||
"views.purchased.sections.download.header" = "First download";
|
"views.purchased.sections.download.header" = "First download";
|
||||||
@ -131,6 +128,10 @@
|
|||||||
"views.ui.purchase_required.purchase.help" = "Purchase required";
|
"views.ui.purchase_required.purchase.help" = "Purchase required";
|
||||||
"views.ui.purchase_required.restricted.help" = "Feature is restricted";
|
"views.ui.purchase_required.restricted.help" = "Feature is restricted";
|
||||||
|
|
||||||
|
"views.vpn.category.any" = "All categories";
|
||||||
|
"views.vpn.preset" = "Preset";
|
||||||
|
"views.vpn.no_servers" = "No servers";
|
||||||
|
|
||||||
// MARK: Views (Modules)
|
// MARK: Views (Modules)
|
||||||
|
|
||||||
"modules.general.sections.storage.header" = "%@";
|
"modules.general.sections.storage.header" = "%@";
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// RefreshInfrastructureButton.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 11/25/24.
|
||||||
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of Passepartout.
|
||||||
|
//
|
||||||
|
// Passepartout is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Passepartout is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import PassepartoutKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct RefreshInfrastructureButton<Label>: View where Label: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var providerManager: ProviderManager
|
||||||
|
|
||||||
|
private let apis: [APIMapper]
|
||||||
|
|
||||||
|
private let providerId: ProviderID
|
||||||
|
|
||||||
|
private let label: () -> Label
|
||||||
|
|
||||||
|
public init(apis: [APIMapper], providerId: ProviderID, label: @escaping () -> Label) {
|
||||||
|
self.apis = apis
|
||||||
|
self.providerId = providerId
|
||||||
|
self.label = label
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
try await providerManager.fetchVPNInfrastructure(from: apis, for: providerId)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
label()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RefreshInfrastructureButton where Label == RefreshInfrastructureButtonProgressView {
|
||||||
|
public init(apis: [APIMapper], providerId: ProviderID) {
|
||||||
|
self.apis = apis
|
||||||
|
self.providerId = providerId
|
||||||
|
label = {
|
||||||
|
RefreshInfrastructureButtonProgressView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct RefreshInfrastructureButtonProgressView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var providerManager: ProviderManager
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
#if os(iOS)
|
||||||
|
HStack {
|
||||||
|
Text(Strings.Views.Providers.refreshInfrastructure)
|
||||||
|
if providerManager.isLoading {
|
||||||
|
Spacer()
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Text(Strings.Views.Providers.refreshInfrastructure)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user