Improve some things about providers (#757)

- iOS: Add category name to clarify servers context
- iOS: Show "No servers" when list is empty
- macOS: Show "Connect" in server selector when presenting from home
- Add last update to issue report
- Refactor provider strings
This commit is contained in:
Davide 2024-10-25 11:38:27 +02:00 committed by GitHub
parent 2c2b3f063a
commit 3abde3851a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 100 additions and 49 deletions

View File

@ -41,7 +41,7 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source", "location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
"state" : { "state" : {
"revision" : "7b3f68b30d2c3ac434c5aaa10853bd3709c5c308" "revision" : "3934b7a4e64624d499f0d52d9053560554bd4be8"
} }
}, },
{ {

View File

@ -28,7 +28,7 @@ let package = Package(
], ],
dependencies: [ dependencies: [
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.0"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.0"),
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "7b3f68b30d2c3ac434c5aaa10853bd3709c5c308"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "3934b7a4e64624d499f0d52d9053560554bd4be8"),
// .package(path: "../../../passepartoutkit-source"), // .package(path: "../../../passepartoutkit-source"),
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.9.1"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.9.1"),
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),

View File

@ -31,6 +31,8 @@ extension Issue {
struct Metadata { struct Metadata {
let profile: Profile? let profile: Profile?
let provider: (ProviderID, Date?)?
let configuration: PassepartoutConfiguration let configuration: PassepartoutConfiguration
let versionString: String let versionString: String
@ -73,7 +75,7 @@ extension Issue {
purchasedProducts: metadata.purchasedProducts, purchasedProducts: metadata.purchasedProducts,
appLog: appLog, appLog: appLog,
tunnelLog: tunnelLog, tunnelLog: tunnelLog,
providerId: metadata.profile?.firstProviderModuleWithMetadata?.1.id provider: metadata.provider
) )
} }
} }

View File

@ -52,12 +52,14 @@ struct Issue: Identifiable {
let providerName: String? let providerName: String?
let providerLastUpdate: Date?
init( init(
appLine: String?, appLine: String?,
purchasedProducts: Set<AppProduct>, purchasedProducts: Set<AppProduct>,
appLog: Data? = nil, appLog: Data? = nil,
tunnelLog: Data? = nil, tunnelLog: Data? = nil,
providerId: ProviderID? = nil provider: (ProviderID, Date?)? = nil
) { ) {
id = UUID() id = UUID()
self.appLine = appLine self.appLine = appLine
@ -85,7 +87,8 @@ struct Issue: Identifiable {
osLine = "\(osName) \(osVersion)" osLine = "\(osName) \(osVersion)"
deviceLine = deviceType deviceLine = deviceType
providerName = providerId?.rawValue providerName = provider?.0.rawValue
providerLastUpdate = provider?.1
} }
var body: String { var body: String {
@ -94,7 +97,7 @@ struct Issue: Identifiable {
.replacingOccurrences(of: "$osLine", with: osLine) .replacingOccurrences(of: "$osLine", with: osLine)
.replacingOccurrences(of: "$deviceLine", with: deviceLine ?? "unknown") .replacingOccurrences(of: "$deviceLine", with: deviceLine ?? "unknown")
.replacingOccurrences(of: "$providerName", with: providerName ?? "none") .replacingOccurrences(of: "$providerName", with: providerName ?? "none")
.replacingOccurrences(of: "$providerLastUpdate", with: "unknown") .replacingOccurrences(of: "$providerLastUpdate", with: providerLastUpdate?.timestamp ?? "unknown")
.replacingOccurrences(of: "$purchasedProducts", with: purchasedProducts.map(\.rawValue).description) .replacingOccurrences(of: "$purchasedProducts", with: purchasedProducts.map(\.rawValue).description)
} }
} }

View File

@ -452,6 +452,36 @@ public enum Strings {
public static let name = Strings.tr("Localizable", "placeholders.profile.name", fallback: "My profile") public static let name = Strings.tr("Localizable", "placeholders.profile.name", fallback: "My profile")
} }
} }
public enum Providers {
/// Clear filters
public static let clearFilters = Strings.tr("Localizable", "providers.clear_filters", fallback: "Clear filters")
/// Last updated on %@
public static func lastUpdated(_ p1: Any) -> String {
return Strings.tr("Localizable", "providers.last_updated", String(describing: p1), fallback: "Last updated on %@")
}
/// None
public static let noProvider = Strings.tr("Localizable", "providers.no_provider", fallback: "None")
/// Refresh infrastructure
public static let refreshInfrastructure = Strings.tr("Localizable", "providers.refresh_infrastructure", fallback: "Refresh infrastructure")
/// Select
public static let selectEntity = Strings.tr("Localizable", "providers.select_entity", fallback: "Select")
/// Select a provider
public static let selectProvider = Strings.tr("Localizable", "providers.select_provider", fallback: "Select a provider")
public enum LastUpdated {
/// Loading...
public static let loading = Strings.tr("Localizable", "providers.last_updated.loading", fallback: "Loading...")
}
public enum Vpn {
/// No servers
public static let noServers = Strings.tr("Localizable", "providers.vpn.no_servers", fallback: "No servers")
/// Preset
public static let preset = Strings.tr("Localizable", "providers.vpn.preset", fallback: "Preset")
public enum Category {
/// All categories
public static let any = Strings.tr("Localizable", "providers.vpn.category.any", fallback: "All categories")
}
}
}
public enum Theme { public enum Theme {
public enum Confirmation { public enum Confirmation {
/// Are you sure? /// Are you sure?
@ -609,30 +639,6 @@ public enum Strings {
public static let newProfile = Strings.tr("Localizable", "views.profiles.toolbar.new_profile", fallback: "New profile") public static let newProfile = Strings.tr("Localizable", "views.profiles.toolbar.new_profile", fallback: "New profile")
} }
} }
public enum Provider {
/// Clear filters
public static let clearFilters = Strings.tr("Localizable", "views.provider.clear_filters", fallback: "Clear filters")
/// None
public static let noProvider = Strings.tr("Localizable", "views.provider.no_provider", fallback: "None")
/// Select a provider
public static let selectProvider = Strings.tr("Localizable", "views.provider.select_provider", fallback: "Select a provider")
/// Select
public static let selectServer = Strings.tr("Localizable", "views.provider.select_server", fallback: "Select")
public enum Vpn {
/// Last updated on %@
public static func lastUpdated(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.provider.vpn.last_updated", String(describing: p1), fallback: "Last updated on %@")
}
/// Preset
public static let preset = Strings.tr("Localizable", "views.provider.vpn.preset", fallback: "Preset")
/// Refresh infrastructure
public static let refreshInfrastructure = Strings.tr("Localizable", "views.provider.vpn.refresh_infrastructure", fallback: "Refresh infrastructure")
public enum LastUpdated {
/// Loading...
public static let loading = Strings.tr("Localizable", "views.provider.vpn.last_updated.loading", fallback: "Loading...")
}
}
}
public enum Settings { public enum Settings {
public enum Rows { public enum Rows {
/// Ask before quit /// Ask before quit

View File

@ -133,15 +133,6 @@
"views.profile.rows.add_module" = "Add module"; "views.profile.rows.add_module" = "Add module";
"views.profile.module_list.section.footer" = "Drag modules to rearrange them, as their order determines priority."; "views.profile.module_list.section.footer" = "Drag modules to rearrange them, as their order determines priority.";
"views.provider.no_provider" = "None";
"views.provider.select_provider" = "Select a provider";
"views.provider.clear_filters" = "Clear filters";
"views.provider.select_server" = "Select";
"views.provider.vpn.refresh_infrastructure" = "Refresh infrastructure";
"views.provider.vpn.last_updated" = "Last updated on %@";
"views.provider.vpn.last_updated.loading" = "Loading...";
"views.provider.vpn.preset" = "Preset";
"views.settings.sections.icloud.footer" = "To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles."; "views.settings.sections.icloud.footer" = "To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.";
"views.settings.rows.confirm_quit" = "Ask before quit"; "views.settings.rows.confirm_quit" = "Ask before quit";
"views.settings.rows.lock_in_background" = "Lock in background"; "views.settings.rows.lock_in_background" = "Lock in background";
@ -232,6 +223,19 @@
"modules.wireguard.preshared_key" = "Pre-shared key"; "modules.wireguard.preshared_key" = "Pre-shared key";
"modules.wireguard.allowed_ips" = "Allowed IPs"; "modules.wireguard.allowed_ips" = "Allowed IPs";
// MARK: - Providers
"providers.no_provider" = "None";
"providers.select_provider" = "Select a provider";
"providers.select_entity" = "Select";
"providers.clear_filters" = "Clear filters";
"providers.refresh_infrastructure" = "Refresh infrastructure";
"providers.last_updated" = "Last updated on %@";
"providers.last_updated.loading" = "Loading...";
"providers.vpn.category.any" = "All categories";
"providers.vpn.preset" = "Preset";
"providers.vpn.no_servers" = "No servers";
// MARK: - Components // MARK: - Components
"ui.connection_status.on_demand_suffix" = " (on-demand)"; "ui.connection_status.on_demand_suffix" = " (on-demand)";

View File

@ -32,6 +32,9 @@ struct ReportIssueButton {
@EnvironmentObject @EnvironmentObject
private var profileManager: ProfileManager private var profileManager: ProfileManager
@EnvironmentObject
private var providerManager: ProviderManager
let tunnel: Tunnel let tunnel: Tunnel
let title: String let title: String
@ -53,4 +56,12 @@ struct ReportIssueButton {
} }
return profileManager.profile(withId: id) return profileManager.profile(withId: id)
} }
var currentProvider: (ProviderID, Date?)? {
guard let id = installedProfile?.firstProviderModuleWithMetadata?.1.id else {
return nil
}
let lastUpdate = providerManager.lastUpdated(for: id)
return (id, lastUpdate)
}
} }

View File

@ -70,6 +70,7 @@ private extension ReportIssueButton {
} }
let issue = await Issue.withMetadata(.init( let issue = await Issue.withMetadata(.init(
profile: installedProfile, profile: installedProfile,
provider: currentProvider,
configuration: .shared, configuration: .shared,
versionString: BundleConfiguration.mainVersionString, versionString: BundleConfiguration.mainVersionString,
purchasedProducts: purchasedProducts, purchasedProducts: purchasedProducts,

View File

@ -51,6 +51,7 @@ private extension ReportIssueButton {
} }
let issue = await Issue.withMetadata(.init( let issue = await Issue.withMetadata(.init(
profile: installedProfile, profile: installedProfile,
provider: currentProvider,
configuration: .shared, configuration: .shared,
versionString: BundleConfiguration.mainVersionString, versionString: BundleConfiguration.mainVersionString,
purchasedProducts: purchasedProducts, purchasedProducts: purchasedProducts,

View File

@ -132,6 +132,7 @@ private extension OpenVPNView {
configurationType: OpenVPN.Configuration.self, configurationType: OpenVPN.Configuration.self,
selectedEntity: providerEntity.wrappedValue, selectedEntity: providerEntity.wrappedValue,
filtersWithSelection: true, filtersWithSelection: true,
selectTitle: Strings.Providers.selectEntity,
onSelect: onSelectServer onSelect: onSelectServer
) )
} }

View File

@ -78,7 +78,7 @@ private extension ProviderContentModifier {
providerRows providerRows
refreshButton { refreshButton {
HStack { HStack {
Text(Strings.Views.Provider.Vpn.refreshInfrastructure) Text(Strings.Providers.refreshInfrastructure)
if providerManager.isLoading { if providerManager.isLoading {
Spacer() Spacer()
ProgressView() ProgressView()
@ -102,7 +102,7 @@ private extension ProviderContentModifier {
} }
Spacer() Spacer()
refreshButton { refreshButton {
Text(Strings.Views.Provider.Vpn.refreshInfrastructure) Text(Strings.Providers.refreshInfrastructure)
} }
} }
} }
@ -138,9 +138,9 @@ private extension ProviderContentModifier {
var lastUpdatedString: String? { var lastUpdatedString: String? {
guard let lastUpdated else { guard let lastUpdated else {
return providerManager.isLoading ? Strings.Views.Provider.Vpn.LastUpdated.loading : nil return providerManager.isLoading ? Strings.Providers.LastUpdated.loading : nil
} }
return Strings.Views.Provider.Vpn.lastUpdated(lastUpdated.timestamp) return Strings.Providers.lastUpdated(lastUpdated.timestamp)
} }
} }

View File

@ -41,7 +41,7 @@ struct ProviderPicker: View {
var body: some View { var body: some View {
Picker(Strings.Global.provider, selection: $providerId) { Picker(Strings.Global.provider, selection: $providerId) {
if !providers.isEmpty { if !providers.isEmpty {
Text(isRequired ? Strings.Views.Provider.selectProvider : Strings.Views.Provider.noProvider) Text(isRequired ? Strings.Providers.selectProvider : Strings.Providers.noProvider)
.tag(nil as ProviderID?) .tag(nil as ProviderID?)
ForEach(providers, id: \.id) { ForEach(providers, id: \.id) {
Text($0.description) Text($0.description)

View File

@ -134,7 +134,7 @@ private extension VPNFiltersView.Subview {
} }
var presetPicker: some View { var presetPicker: some View {
Picker(Strings.Views.Provider.Vpn.preset, selection: $filters.presetId) { Picker(Strings.Providers.Vpn.preset, selection: $filters.presetId) {
Text(Strings.Global.any) Text(Strings.Global.any)
.tag(nil as String?) .tag(nil as String?)
ForEach(presets, id: \.presetId) { ForEach(presets, id: \.presetId) {
@ -145,7 +145,7 @@ private extension VPNFiltersView.Subview {
} }
var clearFiltersButton: some View { var clearFiltersButton: some View {
Button(Strings.Views.Provider.clearFilters, role: .destructive) { Button(Strings.Providers.clearFilters, role: .destructive) {
filters = VPNFilters() filters = VPNFilters()
} }
} }

View File

@ -48,6 +48,7 @@ struct VPNProviderServerCoordinator<Configuration>: View where Configuration: Pr
configurationType: Configuration.self, configurationType: Configuration.self,
selectedEntity: selectedEntity, selectedEntity: selectedEntity,
filtersWithSelection: false, filtersWithSelection: false,
selectTitle: Strings.Global.connect,
onSelect: onSelect onSelect: onSelect
) )
.toolbar { .toolbar {

View File

@ -43,6 +43,8 @@ struct VPNProviderServerView<Configuration>: View where Configuration: ProviderC
let filtersWithSelection: Bool let filtersWithSelection: Bool
let selectTitle: String
let onSelect: (_ server: VPNServer, _ preset: VPNPreset<Configuration>) -> Void let onSelect: (_ server: VPNServer, _ preset: VPNPreset<Configuration>) -> Void
@StateObject @StateObject
@ -64,10 +66,12 @@ struct VPNProviderServerView<Configuration>: View where Configuration: ProviderC
manager: manager, manager: manager,
selectedServer: selectedEntity?.server, selectedServer: selectedEntity?.server,
filters: $filters, filters: $filters,
selectTitle: selectTitle,
onSelect: selectServer onSelect: selectServer
) )
.withErrorHandler(errorHandler) .withErrorHandler(errorHandler)
.navigationTitle(Strings.Global.servers) .navigationTitle(Strings.Global.servers)
.themeNavigationDetail()
.onLoad { .onLoad {
Task { Task {
do { do {
@ -124,6 +128,7 @@ extension VPNProviderServerView {
configurationType: OpenVPN.Configuration.self, configurationType: OpenVPN.Configuration.self,
selectedEntity: nil, selectedEntity: nil,
filtersWithSelection: false, filtersWithSelection: false,
selectTitle: "Select",
onSelect: { _, _ in } onSelect: { _, _ in }
) )
} }

View File

@ -39,6 +39,9 @@ extension VPNProviderServerView {
@Binding @Binding
var filters: VPNFilters var filters: VPNFilters
// unused
let selectTitle: String
let onSelect: (VPNServer) -> Void let onSelect: (VPNServer) -> Void
@State @State
@ -56,11 +59,20 @@ extension VPNProviderServerView {
private extension VPNProviderServerView.Subview { private extension VPNProviderServerView.Subview {
var listView: some View { var listView: some View {
List { ZStack {
if manager.isFiltering { if manager.isFiltering {
ProgressView() ProgressView()
} else if !manager.filteredServers.isEmpty {
List {
Section {
ForEach(countryCodes, id: \.self, content: countryView)
} header: {
Text(filters.categoryName ?? Strings.Providers.Vpn.Category.any)
}
}
} else { } else {
ForEach(countryCodes, id: \.self, content: countryView) Text(Strings.Providers.Vpn.noServers)
.themeEmptyMessage()
} }
} }
.themeAnimation(on: manager.isFiltering, category: .providers) .themeAnimation(on: manager.isFiltering, category: .providers)
@ -144,6 +156,7 @@ private extension VPNProviderServerView.Subview {
configurationType: OpenVPN.Configuration.self, configurationType: OpenVPN.Configuration.self,
selectedEntity: nil, selectedEntity: nil,
filtersWithSelection: false, filtersWithSelection: false,
selectTitle: "Select",
onSelect: { _, _ in } onSelect: { _, _ in }
) )
} }

View File

@ -39,6 +39,8 @@ extension VPNProviderServerView {
@Binding @Binding
var filters: VPNFilters var filters: VPNFilters
let selectTitle: String
let onSelect: (VPNServer) -> Void let onSelect: (VPNServer) -> Void
var body: some View { var body: some View {
@ -72,7 +74,7 @@ private extension VPNProviderServerView.Subview {
Button { Button {
onSelect(server) onSelect(server)
} label: { } label: {
Text(Strings.Views.Provider.selectServer) Text(selectTitle)
} }
} }
} }
@ -98,6 +100,7 @@ private extension VPNProviderServerView.Subview {
configurationType: OpenVPN.Configuration.self, configurationType: OpenVPN.Configuration.self,
selectedEntity: nil, selectedEntity: nil,
filtersWithSelection: false, filtersWithSelection: false,
selectTitle: "Select",
onSelect: { _, _ in } onSelect: { _, _ in }
) )
} }