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",
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
"state" : {
"revision" : "7b3f68b30d2c3ac434c5aaa10853bd3709c5c308"
"revision" : "3934b7a4e64624d499f0d52d9053560554bd4be8"
}
},
{

View File

@ -28,7 +28,7 @@ let package = Package(
],
dependencies: [
// .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(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"),

View File

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

View File

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

View File

@ -452,6 +452,36 @@ public enum Strings {
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 Confirmation {
/// 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 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 Rows {
/// Ask before quit

View File

@ -133,15 +133,6 @@
"views.profile.rows.add_module" = "Add module";
"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.rows.confirm_quit" = "Ask before quit";
"views.settings.rows.lock_in_background" = "Lock in background";
@ -232,6 +223,19 @@
"modules.wireguard.preshared_key" = "Pre-shared key";
"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
"ui.connection_status.on_demand_suffix" = " (on-demand)";

View File

@ -32,6 +32,9 @@ struct ReportIssueButton {
@EnvironmentObject
private var profileManager: ProfileManager
@EnvironmentObject
private var providerManager: ProviderManager
let tunnel: Tunnel
let title: String
@ -53,4 +56,12 @@ struct ReportIssueButton {
}
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(
profile: installedProfile,
provider: currentProvider,
configuration: .shared,
versionString: BundleConfiguration.mainVersionString,
purchasedProducts: purchasedProducts,

View File

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

View File

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

View File

@ -78,7 +78,7 @@ private extension ProviderContentModifier {
providerRows
refreshButton {
HStack {
Text(Strings.Views.Provider.Vpn.refreshInfrastructure)
Text(Strings.Providers.refreshInfrastructure)
if providerManager.isLoading {
Spacer()
ProgressView()
@ -102,7 +102,7 @@ private extension ProviderContentModifier {
}
Spacer()
refreshButton {
Text(Strings.Views.Provider.Vpn.refreshInfrastructure)
Text(Strings.Providers.refreshInfrastructure)
}
}
}
@ -138,9 +138,9 @@ private extension ProviderContentModifier {
var lastUpdatedString: String? {
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 {
Picker(Strings.Global.provider, selection: $providerId) {
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?)
ForEach(providers, id: \.id) {
Text($0.description)

View File

@ -134,7 +134,7 @@ private extension VPNFiltersView.Subview {
}
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)
.tag(nil as String?)
ForEach(presets, id: \.presetId) {
@ -145,7 +145,7 @@ private extension VPNFiltersView.Subview {
}
var clearFiltersButton: some View {
Button(Strings.Views.Provider.clearFilters, role: .destructive) {
Button(Strings.Providers.clearFilters, role: .destructive) {
filters = VPNFilters()
}
}

View File

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

View File

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

View File

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

View File

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