diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index 7cd87d60..f5dc6547 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -34,7 +34,7 @@ 0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; }; 0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */; }; 0E3CD47F280DA14B007075C0 /* OrganizerView+AddMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CD47E280DA14B007075C0 /* OrganizerView+AddMenu.swift */; }; - 0E3CD483280DAE92007075C0 /* ProfileView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CD482280DAE92007075C0 /* ProfileView+Toolbar.swift */; }; + 0E3CD483280DAE92007075C0 /* ProfileView+MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CD482280DAE92007075C0 /* ProfileView+MainMenu.swift */; }; 0E44689627B051C300A14CE4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689527B051C300A14CE4 /* ProfileView.swift */; }; 0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689B27B11B5300A14CE4 /* AboutView.swift */; }; 0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */; }; @@ -64,7 +64,6 @@ 0E71ACFB27C12E5300F85C4B /* VersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACFA27C12E5300F85C4B /* VersionView.swift */; }; 0E71ACFD27C1321A00F85C4B /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */; }; 0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7577D62816A3B200081CBE /* DestructiveButton.swift */; }; - 0E7577DD2816C3AD00081CBE /* ProfileView+Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7577DC2816C3AD00081CBE /* ProfileView+Buttons.swift */; }; 0E7577DF2817E22C00081CBE /* VPNToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7577DE2817E22C00081CBE /* VPNToggle.swift */; }; 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */; }; 0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */; }; @@ -220,7 +219,7 @@ 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = ""; }; 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Provider.swift"; sourceTree = ""; }; 0E3CD47E280DA14B007075C0 /* OrganizerView+AddMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+AddMenu.swift"; sourceTree = ""; }; - 0E3CD482280DAE92007075C0 /* ProfileView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Toolbar.swift"; sourceTree = ""; }; + 0E3CD482280DAE92007075C0 /* ProfileView+MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+MainMenu.swift"; sourceTree = ""; }; 0E44689527B051C300A14CE4 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; 0E44689B27B11B5300A14CE4 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointAdvancedView+OpenVPN.swift"; sourceTree = ""; }; @@ -251,7 +250,6 @@ 0E71ACFA27C12E5300F85C4B /* VersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionView.swift; sourceTree = ""; }; 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; 0E7577D62816A3B200081CBE /* DestructiveButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestructiveButton.swift; sourceTree = ""; }; - 0E7577DC2816C3AD00081CBE /* ProfileView+Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Buttons.swift"; sourceTree = ""; }; 0E7577DE2817E22C00081CBE /* VPNToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNToggle.swift; sourceTree = ""; }; 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostViewModel.swift; sourceTree = ""; }; 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Configuration.swift"; sourceTree = ""; }; @@ -454,13 +452,12 @@ 0ED30DD127EA1F650057D8A3 /* PaywallView+Purchase.swift */, 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */, 0E44689527B051C300A14CE4 /* ProfileView.swift */, - 0E7577DC2816C3AD00081CBE /* ProfileView+Buttons.swift */, 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */, 0E92D7F327F104B80033CB7B /* ProfileView+Diagnostics.swift */, 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */, + 0E3CD482280DAE92007075C0 /* ProfileView+MainMenu.swift */, 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */, 0EBC074B27EB673C00208AD9 /* ProfileView+Rename.swift */, - 0E3CD482280DAE92007075C0 /* ProfileView+Toolbar.swift */, 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */, 0E71ACF027C1073800F85C4B /* ProviderLocationView.swift */, 0E71ACEE27C106B400F85C4B /* ProviderPresetView.swift */, @@ -902,7 +899,6 @@ 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */, 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */, 0EF708322811CC8400A3A308 /* VPNStatusText.swift in Sources */, - 0E7577DD2816C3AD00081CBE /* ProfileView+Buttons.swift in Sources */, 0E5324A627D297BB002565C3 /* InApp.swift in Sources */, 0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */, 0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */, @@ -967,7 +963,7 @@ 0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */, 0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */, 0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */, - 0E3CD483280DAE92007075C0 /* ProfileView+Toolbar.swift in Sources */, + 0E3CD483280DAE92007075C0 /* ProfileView+MainMenu.swift in Sources */, 0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */, 0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */, 0E53249927D26B51002565C3 /* ProductManager.swift in Sources */, diff --git a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme index 5824feb5..8649b99f 100644 --- a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme +++ b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme @@ -126,7 +126,7 @@ String { return L10n.tr("Localizable", "organizer.alerts.remove_profile.message", String(describing: p1)) } - /// Remove profile - internal static let title = L10n.tr("Localizable", "organizer.alerts.remove_profile.title") } } internal enum Empty { @@ -815,10 +817,6 @@ internal enum L10n { /// Reconnect internal static let caption = L10n.tr("Localizable", "profile.items.reconnect.caption") } - internal enum Uninstall { - /// Remove VPN configuration - internal static let caption = L10n.tr("Localizable", "profile.items.uninstall.caption") - } internal enum UseProfile { /// Use this profile internal static let caption = L10n.tr("Localizable", "profile.items.use_profile.caption") diff --git a/Passepartout/App/Constants/Theme.swift b/Passepartout/App/Constants/Theme.swift index a5631e73..8314b20a 100644 --- a/Passepartout/App/Constants/Theme.swift +++ b/Passepartout/App/Constants/Theme.swift @@ -172,6 +172,10 @@ extension View { "xmark" } + var themeUninstallImage: String { + "arrow.uturn.down" + } + var themeDeleteImage: String { "trash.fill" } @@ -281,10 +285,6 @@ extension View { foregroundColor(themeLightTextColor) } - func themeDestructiveButtonStyle() -> some View { - foregroundColor(themeErrorColor) - } - @available(iOS 15, *) func themePrimaryTintStyle() -> some View { tint(themePrimaryBackgroundColor) diff --git a/Passepartout/App/Views/OrganizerView+Profiles.swift b/Passepartout/App/Views/OrganizerView+Profiles.swift index f6a880ab..d0ec1eba 100644 --- a/Passepartout/App/Views/OrganizerView+Profiles.swift +++ b/Passepartout/App/Views/OrganizerView+Profiles.swift @@ -109,11 +109,7 @@ extension OrganizerView { isActive: profileManager.isActiveProfile(header.id) ) }.contextMenu { - Button { - duplicateProfile(withId: header.id) - } label: { - Label(L10n.Global.Strings.duplicate, systemImage: themeDuplicateImage) - } + ProfileView.DuplicateButton(header: header) }.themeTextButtonStyle() } @@ -194,10 +190,6 @@ extension OrganizerView.ProfilesList { profileManager.removeProfiles(withIds: toDelete) } - private func duplicateProfile(withId id: UUID) { - profileManager.duplicateProfile(withId: id) - } - private func performMigrationsIfNeeded() { Task { await appManager.doMigrations(profileManager) diff --git a/Passepartout/App/Views/ProfileView+Buttons.swift b/Passepartout/App/Views/ProfileView+Buttons.swift deleted file mode 100644 index 09900f36..00000000 --- a/Passepartout/App/Views/ProfileView+Buttons.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// ProfileView+Buttons.swift -// Passepartout -// -// Created by Davide De Rosa on 4/25/22. -// Copyright (c) 2022 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 . -// - -import SwiftUI -import PassepartoutCore - -extension ProfileView { - struct RemoveProfileButton: View { - @ObservedObject private var profileManager: ProfileManager - - private let header: Profile.Header - - @State private var isConfirming = false - - private let title = L10n.Organizer.Alerts.RemoveProfile.title - - init(header: Profile.Header) { - profileManager = .shared - self.header = header - } - - var body: some View { - DestructiveButton { - isConfirming = true - } label: { - Label(title, systemImage: themeDeleteImage) - }.actionSheet(isPresented: $isConfirming) { - ActionSheet( - title: Text(L10n.Organizer.Alerts.RemoveProfile.message(header.name)), - message: nil, - buttons: [ - .destructive(Text(title), action: removeProfile), - .cancel(Text(L10n.Global.Strings.cancel)) - ] - ) - }.themeDestructiveButtonStyle() - } - - private func removeProfile() { - profileManager.removeProfiles(withIds: [header.id]) - } - } - - struct UninstallVPNButton: View { - @ObservedObject private var vpnManager: VPNManager - - @State private var isConfirming = false - - init() { - vpnManager = .shared - } - - var body: some View { - DestructiveButton { - isConfirming = true - } label: { - Label(L10n.Profile.Items.Uninstall.caption, systemImage: themeDeleteImage) - }.actionSheet(isPresented: $isConfirming) { - ActionSheet( - title: Text(L10n.Profile.Alerts.UninstallVpn.message), - message: nil, - buttons: [ - .destructive(Text(L10n.Profile.Items.Uninstall.caption), action: uninstallVPN), - .cancel(Text(L10n.Global.Strings.cancel)) - ] - ) - }.themeDestructiveButtonStyle() - } - - private func uninstallVPN() { - Task { - await vpnManager.uninstall() - } - } - } -} diff --git a/Passepartout/App/Views/ProfileView+MainMenu.swift b/Passepartout/App/Views/ProfileView+MainMenu.swift new file mode 100644 index 00000000..d4a8a073 --- /dev/null +++ b/Passepartout/App/Views/ProfileView+MainMenu.swift @@ -0,0 +1,208 @@ +// +// ProfileView+MainMenu.swift +// Passepartout +// +// Created by Davide De Rosa on 2/6/22. +// Copyright (c) 2022 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 . +// + +import SwiftUI +import PassepartoutCore + +extension ProfileView { + struct MainMenu: View { + enum ActionSheetType: Int, Identifiable { + case uninstallVPN + + case deleteProfile + + var id: Int { + rawValue + } + } + + @ObservedObject private var profileManager: ProfileManager + + @ObservedObject private var vpnManager: VPNManager + + @ObservedObject private var currentProfile: ObservableProfile + + private var header: Profile.Header { + currentProfile.value.header + } + + @Binding private var modalType: ModalType? + + @State private var actionSheetType: ActionSheetType? + + private let uninstallVPNTitle = L10n.Global.Strings.uninstall + + private let deleteProfileTitle = L10n.Global.Strings.delete + + private let cancelTitle = L10n.Global.Strings.cancel + + init(currentProfile: ObservableProfile, modalType: Binding) { + profileManager = .shared + vpnManager = .shared + self.currentProfile = currentProfile + _modalType = modalType + } + + var body: some View { + Menu { + RenameButton( + modalType: $modalType + ) + // should contextually set duplicate as current profile +// DuplicateButton( +// header: currentProfile.value.header +// ) + ShortcutsButton( + modalType: $modalType + ) + uninstallVPNButton + Divider() + deleteProfileButton + } label: { + themeSettingsMenuImage.asSystemImage + }.actionSheet(item: $actionSheetType) { + switch $0 { + case .uninstallVPN: + return ActionSheet( + title: Text(L10n.Profile.Alerts.UninstallVpn.message), + message: nil, + buttons: [ + .destructive(Text(uninstallVPNTitle), action: uninstallVPN), + .cancel(Text(cancelTitle)) + ] + ) + + case .deleteProfile: + return ActionSheet( + title: Text(L10n.Organizer.Alerts.RemoveProfile.message(header.name)), + message: nil, + buttons: [ + .destructive(Text(deleteProfileTitle), action: removeProfile), + .cancel(Text(cancelTitle)) + ] + ) + } + } + } + + private var uninstallVPNButton: some View { + Button { + actionSheetType = .uninstallVPN + } label: { + Label(uninstallVPNTitle, systemImage: themeUninstallImage) + } + } + + private var deleteProfileButton: some View { + DestructiveButton { + actionSheetType = .deleteProfile + } label: { + Label(deleteProfileTitle, systemImage: themeDeleteImage) + } + } + + private func uninstallVPN() { + Task { + await vpnManager.uninstall() + } + } + + private func removeProfile() { + profileManager.removeProfiles(withIds: [header.id]) + } + } + + struct RenameButton: View { + @Binding private var modalType: ModalType? + + init(modalType: Binding) { + _modalType = modalType + } + + var body: some View { + Button { + modalType = .rename + } label: { + Label(L10n.Global.Strings.rename, systemImage: themeRenameProfileImage) + } + } + } + + struct ShortcutsButton: View { + @ObservedObject private var productManager: ProductManager + + @Binding private var modalType: ModalType? + + init(modalType: Binding) { + productManager = .shared + _modalType = modalType + } + + private var isEligibleForSiri: Bool { + productManager.isEligible(forFeature: .siriShortcuts) + } + + var body: some View { + Button { + presentShortcutsOrPaywall() + } label: { + Label(Unlocalized.Other.siri, systemImage: themeShortcutsImage) + } + } + + private func presentShortcutsOrPaywall() { + + // eligibility: enter Siri shortcuts or present paywall + if isEligibleForSiri { + modalType = .shortcuts + } else { + modalType = .paywallShortcuts + } + } + } + + struct DuplicateButton: View { + @ObservedObject private var profileManager: ProfileManager + + let header: Profile.Header + + init(header: Profile.Header) { + profileManager = .shared + self.header = header + } + + var body: some View { + Button { + duplicateProfile(withId: header.id) + } label: { + Label(L10n.Global.Strings.duplicate, systemImage: themeDuplicateImage) + } + } + + private func duplicateProfile(withId id: UUID) { + profileManager.duplicateProfile(withId: id) + } + } +} diff --git a/Passepartout/App/Views/ProfileView+Toolbar.swift b/Passepartout/App/Views/ProfileView+Toolbar.swift deleted file mode 100644 index 28c4a13c..00000000 --- a/Passepartout/App/Views/ProfileView+Toolbar.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// ProfileView+Toolbar.swift -// Passepartout -// -// Created by Davide De Rosa on 2/6/22. -// Copyright (c) 2022 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 . -// - -import SwiftUI -import PassepartoutCore - -extension ProfileView { - struct ShortcutsItem: View { - @ObservedObject private var productManager: ProductManager - - @Binding private var modalType: ModalType? - - init(modalType: Binding) { - productManager = .shared - _modalType = modalType - } - - private var isEligibleForSiri: Bool { - productManager.isEligible(forFeature: .siriShortcuts) - } - - var body: some View { - Button { - presentShortcutsOrPaywall() - } label: { - themeShortcutsImage.asSystemImage - } - } - - private func presentShortcutsOrPaywall() { - - // eligibility: enter Siri shortcuts or present paywall - if isEligibleForSiri { - modalType = .shortcuts - } else { - modalType = .paywallShortcuts - } - } - } - - struct RenameItem: View { - @ObservedObject private var currentProfile: ObservableProfile - - @Binding private var modalType: ModalType? - - init(currentProfile: ObservableProfile, modalType: Binding) { - self.currentProfile = currentProfile - _modalType = modalType - } - - var body: some View { - Button { - modalType = .rename - } label: { - themeRenameProfileImage.asSystemImage - } - } - } -} diff --git a/Passepartout/App/Views/ProfileView.swift b/Passepartout/App/Views/ProfileView.swift index f4db67b4..76ced039 100644 --- a/Passepartout/App/Views/ProfileView.swift +++ b/Passepartout/App/Views/ProfileView.swift @@ -70,16 +70,10 @@ struct ProfileView: View { WelcomeView() } }.toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - ShortcutsItem( - modalType: $modalType - ).disabled(!isExisting) - - RenameItem( - currentProfile: profileManager.currentProfile, - modalType: $modalType - ).disabled(!isExisting) - } + MainMenu( + currentProfile: profileManager.currentProfile, + modalType: $modalType + ).disabled(!isExisting) }.sheet(item: $modalType, content: presentedModal) .navigationTitle(title) .themeSecondaryView() @@ -103,10 +97,6 @@ struct ProfileView: View { ) ExtraSection(currentProfile: profileManager.currentProfile) DiagnosticsSection(currentProfile: profileManager.currentProfile) - Section { - UninstallVPNButton() - RemoveProfileButton(header: profileManager.currentProfile.value.header) - } } } } diff --git a/Passepartout/App/en.lproj/Localizable.strings b/Passepartout/App/en.lproj/Localizable.strings index 150411a9..5507d2af 100644 --- a/Passepartout/App/en.lproj/Localizable.strings +++ b/Passepartout/App/en.lproj/Localizable.strings @@ -14,6 +14,8 @@ "global.strings.rename" = "Rename"; "global.strings.duplicate" = "Duplicate"; "global.strings.add" = "Add"; +"global.strings.delete" = "Delete"; +"global.strings.uninstall" = "Uninstall"; "global.strings.default" = "Default"; "global.strings.name" = "Name"; "global.strings.profiles" = "Profiles"; @@ -123,7 +125,6 @@ "organizer.alerts.reddit.buttons.remind" = "Remind me later"; "organizer.alerts.reddit.buttons.never" = "Don't ask again"; -"organizer.alerts.remove_profile.title" = "Remove profile"; "organizer.alerts.remove_profile.message" = "Are you sure you want to delete profile %@?"; /* MARK: AddProfileView */ @@ -164,7 +165,6 @@ "profile.items.vpn_survives_sleep.caption" = "Keep alive on sleep"; "profile.items.vpn_resolves_hostname.caption" = "Resolve provider hostname"; "profile.items.reconnect.caption" = "Reconnect"; -"profile.items.uninstall.caption" = "Remove VPN configuration"; "profile.alerts.rename.title" = "Rename profile"; "profile.alerts.reconnect_vpn.message" = "Do you want to reconnect to the VPN?"; diff --git a/res/ios/snap-profile.png b/res/ios/snap-profile.png index 6b345523..4b5502e3 100644 Binary files a/res/ios/snap-profile.png and b/res/ios/snap-profile.png differ