From 6434008ebdecca832e5fe72b47ade32eb65920f2 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Mon, 25 Apr 2022 11:40:13 +0200 Subject: [PATCH] Make destructive buttons standalone - Uninstall VPN - Remove profile (add to ProfileView) Create DestructiveButton with iOS 15 .role when available. --- Passepartout.xcodeproj/project.pbxproj | 8 ++ Passepartout/App/Constants/Theme.swift | 4 - .../App/Reusable/DestructiveButton.swift | 40 ++++++++ .../App/Views/ProfileView+Buttons.swift | 98 +++++++++++++++++++ Passepartout/App/Views/ProfileView+VPN.swift | 34 ------- Passepartout/App/Views/ProfileView.swift | 5 +- 6 files changed, 150 insertions(+), 39 deletions(-) create mode 100644 Passepartout/App/Reusable/DestructiveButton.swift create mode 100644 Passepartout/App/Views/ProfileView+Buttons.swift diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index b5170069..736e168e 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -61,6 +61,8 @@ 0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACF827C12E4800F85C4B /* CreditsView.swift */; }; 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 */; }; 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */; }; 0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */; }; 0E92D7C927F1042A0033CB7B /* ProfileView+Extra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */; }; @@ -243,6 +245,8 @@ 0E71ACF827C12E4800F85C4B /* CreditsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsView.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Extra.swift"; sourceTree = ""; }; @@ -373,6 +377,7 @@ 0EB4042D27CA136200378B1A /* AddingTextField.swift */, 0EB3412F27C7761A00483410 /* Binding+Extensions.swift */, 0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */, + 0E7577D62816A3B200081CBE /* DestructiveButton.swift */, 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */, 0E2C171A27CB5A3A007E8488 /* GenericCreditsView.swift */, 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */, @@ -442,6 +447,7 @@ 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 */, @@ -887,8 +893,10 @@ 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.swift in Sources */, + 0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */, 0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */, 0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */, 0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */, diff --git a/Passepartout/App/Constants/Theme.swift b/Passepartout/App/Constants/Theme.swift index 403f7fe8..c07e0dfc 100644 --- a/Passepartout/App/Constants/Theme.swift +++ b/Passepartout/App/Constants/Theme.swift @@ -277,10 +277,6 @@ extension View { foregroundColor(themeLightTextColor) } - func themeErrorTextStyle() -> some View { - foregroundColor(themeErrorColor) - } - func themeDestructiveButtonStyle() -> some View { foregroundColor(themeErrorColor) } diff --git a/Passepartout/App/Reusable/DestructiveButton.swift b/Passepartout/App/Reusable/DestructiveButton.swift new file mode 100644 index 00000000..1dfbeb9d --- /dev/null +++ b/Passepartout/App/Reusable/DestructiveButton.swift @@ -0,0 +1,40 @@ +// +// DestructiveButton.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 + +struct DestructiveButton: View { + let action: () -> Void + + let label: () -> Label + + var body: some View { + if #available(iOS 15, *) { + Button(role: .destructive, action: action, label: label) + } else { + Button(action: action, label: label) + } + } +} diff --git a/Passepartout/App/Views/ProfileView+Buttons.swift b/Passepartout/App/Views/ProfileView+Buttons.swift new file mode 100644 index 00000000..09900f36 --- /dev/null +++ b/Passepartout/App/Views/ProfileView+Buttons.swift @@ -0,0 +1,98 @@ +// +// 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+VPN.swift b/Passepartout/App/Views/ProfileView+VPN.swift index f2a49f57..b5389a23 100644 --- a/Passepartout/App/Views/ProfileView+VPN.swift +++ b/Passepartout/App/Views/ProfileView+VPN.swift @@ -149,38 +149,4 @@ extension ProfileView { } } } - - struct UninstallVPNSection: View { - @ObservedObject private var vpnManager: VPNManager - - @State private var isAskingUninstallVPN = false - - init() { - vpnManager = .shared - } - - var body: some View { - Section { - Button { - isAskingUninstallVPN = true - } label: { - Label(L10n.Profile.Items.Uninstall.caption, systemImage: themeDeleteImage) - }.themeErrorTextStyle() - .actionSheet(isPresented: $isAskingUninstallVPN) { - ActionSheet( - title: Text(L10n.Profile.Alerts.UninstallVpn.message), - message: nil, - buttons: [ - .destructive(Text(L10n.Profile.Items.Uninstall.caption), action: { - Task { - await vpnManager.uninstall() - } - }), - .cancel(Text(L10n.Global.Strings.cancel)) - ] - ) - } - } - } - } } diff --git a/Passepartout/App/Views/ProfileView.swift b/Passepartout/App/Views/ProfileView.swift index e1e35701..6ca10130 100644 --- a/Passepartout/App/Views/ProfileView.swift +++ b/Passepartout/App/Views/ProfileView.swift @@ -103,7 +103,10 @@ struct ProfileView: View { ) ExtraSection(currentProfile: profileManager.currentProfile) DiagnosticsSection(currentProfile: profileManager.currentProfile) - UninstallVPNSection() + Section { + UninstallVPNButton() + RemoveProfileButton(header: profileManager.currentProfile.value.header) + } } } }