Group Organizer modals into toolbar menus
- Drop status / navigation bars colors - Restore large title on iPad - Overlay organizer with "No profiles" when empty - Uninstall VPN from ProfileView
This commit is contained in:
parent
6533a6beae
commit
18161ed1f1
|
@ -24,15 +24,13 @@
|
|||
0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */; };
|
||||
0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */; };
|
||||
0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */; };
|
||||
0E34AC7627F83FE20042F2AB /* OrganizerView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7527F83FE20042F2AB /* OrganizerView+VPN.swift */; };
|
||||
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; };
|
||||
0E34AC7A27F8431D0042F2AB /* OrganizerView+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7927F8431D0042F2AB /* OrganizerView+Shortcuts.swift */; };
|
||||
0E34AC7C27F845510042F2AB /* OrganizerView+Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */; };
|
||||
0E34AC7E27F849050042F2AB /* OrganizerView+AddProfileMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7D27F849050042F2AB /* OrganizerView+AddProfileMenu.swift */; };
|
||||
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
|
||||
0E3B7FCD27E47B3700C66F13 /* AddHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
|
@ -110,6 +108,7 @@
|
|||
0ED89C2027DE423B008B36D6 /* ShortcutsView+ConnectTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */; };
|
||||
0ED89C2527DE45A3008B36D6 /* ProfileHeaderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */; };
|
||||
0EDE02C227F61C79000FBE3C /* EditableTextList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */; };
|
||||
0EE11CD2280D8317003BE431 /* OrganizerView+SettingsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE11CD1280D8317003BE431 /* OrganizerView+SettingsMenu.swift */; };
|
||||
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */; };
|
||||
0EED0BB92733CEDA00C9FC68 /* PassepartoutCore in Frameworks */ = {isa = PBXBuildFile; productRef = 0EED0BB82733CEDA00C9FC68 /* PassepartoutCore */; };
|
||||
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF527DD0211007EB181 /* PaywallView.swift */; };
|
||||
|
@ -205,15 +204,13 @@
|
|||
0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenVPN+L10n.swift"; sourceTree = "<group>"; };
|
||||
0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Core+L10n.swift"; sourceTree = "<group>"; };
|
||||
0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericVersionView.swift; sourceTree = "<group>"; };
|
||||
0E34AC7527F83FE20042F2AB /* OrganizerView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+VPN.swift"; sourceTree = "<group>"; };
|
||||
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; };
|
||||
0E34AC7927F8431D0042F2AB /* OrganizerView+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Shortcuts.swift"; sourceTree = "<group>"; };
|
||||
0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Profiles.swift"; sourceTree = "<group>"; };
|
||||
0E34AC7D27F849050042F2AB /* OrganizerView+AddProfileMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+AddProfileMenu.swift"; sourceTree = "<group>"; };
|
||||
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; };
|
||||
0E3B7FCC27E47B3700C66F13 /* AddHostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostView.swift; sourceTree = "<group>"; };
|
||||
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
|
||||
0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Provider.swift"; sourceTree = "<group>"; };
|
||||
0E3CD47E280DA14B007075C0 /* OrganizerView+AddMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+AddMenu.swift"; sourceTree = "<group>"; };
|
||||
0E44689527B051C300A14CE4 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
0E44689B27B11B5300A14CE4 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointAdvancedView+OpenVPN.swift"; sourceTree = "<group>"; };
|
||||
|
@ -325,6 +322,7 @@
|
|||
0EDE8DC320C86910004C739C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0EDE8DD220C86978004C739C /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; };
|
||||
0EDE8DE220C86A13004C739C /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
|
||||
0EE11CD1280D8317003BE431 /* OrganizerView+SettingsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+SettingsMenu.swift"; sourceTree = "<group>"; };
|
||||
0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VPNProtocolType+FileExtensions.swift"; sourceTree = "<group>"; };
|
||||
0EF0FAF527DD0211007EB181 /* PaywallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallView.swift; sourceTree = "<group>"; };
|
||||
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentActivity.swift; sourceTree = "<group>"; };
|
||||
|
@ -391,11 +389,10 @@
|
|||
0EB34BC927C6A70200B126DA /* OnDemandView.swift */,
|
||||
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */,
|
||||
0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */,
|
||||
0E34AC7D27F849050042F2AB /* OrganizerView+AddProfileMenu.swift */,
|
||||
0E3CD47E280DA14B007075C0 /* OrganizerView+AddMenu.swift */,
|
||||
0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */,
|
||||
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */,
|
||||
0E34AC7927F8431D0042F2AB /* OrganizerView+Shortcuts.swift */,
|
||||
0E34AC7527F83FE20042F2AB /* OrganizerView+VPN.swift */,
|
||||
0EE11CD1280D8317003BE431 /* OrganizerView+SettingsMenu.swift */,
|
||||
0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */,
|
||||
0E44689527B051C300A14CE4 /* ProfileView.swift */,
|
||||
0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */,
|
||||
|
@ -937,8 +934,6 @@
|
|||
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */,
|
||||
0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */,
|
||||
0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */,
|
||||
0E34AC7A27F8431D0042F2AB /* OrganizerView+Shortcuts.swift in Sources */,
|
||||
0E34AC7E27F849050042F2AB /* OrganizerView+AddProfileMenu.swift in Sources */,
|
||||
0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Identifiable.swift in Sources */,
|
||||
0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */,
|
||||
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */,
|
||||
|
@ -964,6 +959,7 @@
|
|||
0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */,
|
||||
0E9C233327F47E95007D5FC7 /* IntentDispatcher+Activities.swift in Sources */,
|
||||
0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */,
|
||||
0E3CD47F280DA14B007075C0 /* OrganizerView+AddMenu.swift in Sources */,
|
||||
0EB17EAA27D226C900D473B5 /* Constants+Extensions.swift in Sources */,
|
||||
0E53E63727E34FE2001D4902 /* AppContext.swift in Sources */,
|
||||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
||||
|
@ -986,7 +982,6 @@
|
|||
0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */,
|
||||
0E71ACEF27C106B500F85C4B /* ProviderPresetView.swift in Sources */,
|
||||
0E0AD49027BD53CB00FBB520 /* ProfileView+Welcome.swift in Sources */,
|
||||
0E34AC7627F83FE20042F2AB /* OrganizerView+VPN.swift in Sources */,
|
||||
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */,
|
||||
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */,
|
||||
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */,
|
||||
|
@ -1001,6 +996,7 @@
|
|||
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */,
|
||||
0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */,
|
||||
0EE11CD2280D8317003BE431 /* OrganizerView+SettingsMenu.swift in Sources */,
|
||||
0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */,
|
||||
0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */,
|
||||
0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */,
|
||||
|
|
|
@ -37,28 +37,6 @@ extension Color {
|
|||
}
|
||||
|
||||
extension View {
|
||||
|
||||
@available(iOS 14, *)
|
||||
func themeConfigureNavigationBarAppearance() {
|
||||
let navBackgroundColor = Asset.Assets.primaryColor.color
|
||||
let titleAttributes: [NSAttributedString.Key: Any] = [
|
||||
.foregroundColor: Asset.Assets.lightTextColor.color
|
||||
]
|
||||
|
||||
let navBarAppearance = UINavigationBarAppearance()
|
||||
navBarAppearance.configureWithOpaqueBackground()
|
||||
navBarAppearance.backgroundColor = navBackgroundColor
|
||||
navBarAppearance.titleTextAttributes = titleAttributes
|
||||
navBarAppearance.largeTitleTextAttributes = titleAttributes
|
||||
|
||||
let navBar = UINavigationBar.appearance()
|
||||
navBar.standardAppearance = navBarAppearance
|
||||
navBar.compactAppearance = navBarAppearance
|
||||
navBar.scrollEdgeAppearance = navBarAppearance
|
||||
|
||||
// UITableView.appearance().backgroundColor = .clear
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
var themeIdiom: UIUserInterfaceIdiom {
|
||||
UIDevice.current.userInterfaceIdiom
|
||||
|
@ -78,7 +56,7 @@ extension View {
|
|||
|
||||
@ViewBuilder
|
||||
private func themeNavigationViewStyle() -> some View {
|
||||
switch UIDevice.current.userInterfaceIdiom {
|
||||
switch themeIdiom {
|
||||
case .phone:
|
||||
navigationViewStyle(.stack)
|
||||
|
||||
|
@ -87,15 +65,8 @@ extension View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func themePrimaryView() -> some View {
|
||||
switch UIDevice.current.userInterfaceIdiom {
|
||||
case .phone:
|
||||
navigationBarTitleDisplayMode(.large)
|
||||
|
||||
default:
|
||||
themeSecondaryView()
|
||||
}
|
||||
navigationBarTitleDisplayMode(.large)
|
||||
}
|
||||
|
||||
func themeSecondaryView() -> some View {
|
||||
|
@ -106,6 +77,11 @@ extension View {
|
|||
lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
}
|
||||
|
||||
func themeInformativeText() -> some View {
|
||||
font(.title)
|
||||
.foregroundColor(themeSecondaryColor)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Colors
|
||||
|
@ -180,10 +156,14 @@ extension View {
|
|||
"externaldrive.connected.to.line.below.fill"
|
||||
}
|
||||
|
||||
var themeAddProfileImage: String {
|
||||
var themeSettingsMenuImage: String {
|
||||
"ellipsis.circle"
|
||||
}
|
||||
|
||||
var themeAddMenuImage: String {
|
||||
"plus"
|
||||
}
|
||||
|
||||
|
||||
var themeCheckmarkImage: String {
|
||||
"checkmark"
|
||||
}
|
||||
|
|
|
@ -103,6 +103,10 @@ class ProductManager: NSObject, ObservableObject {
|
|||
SKPaymentQueue.default().remove(self)
|
||||
}
|
||||
|
||||
func canMakePayments() -> Bool {
|
||||
SKPaymentQueue.canMakePayments()
|
||||
}
|
||||
|
||||
func refreshProducts() {
|
||||
let ids = LocalProduct.all
|
||||
guard !ids.isEmpty else {
|
||||
|
|
|
@ -78,8 +78,6 @@
|
|||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
@ -93,8 +91,6 @@
|
|||
</array>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>com.algoritmico.Passepartout.config</key>
|
||||
<dict>
|
||||
<key>appstore_id</key>
|
||||
|
|
|
@ -28,7 +28,7 @@ import TunnelKitManager
|
|||
import TunnelKitOpenVPN
|
||||
import TunnelKitWireGuard
|
||||
import NetworkExtension
|
||||
import PassepartoutUtils
|
||||
import PassepartoutCore
|
||||
|
||||
extension VPNStatus {
|
||||
var localizedDescription: String {
|
||||
|
|
|
@ -26,17 +26,6 @@
|
|||
import SwiftUI
|
||||
|
||||
struct AboutView: View {
|
||||
enum ModalType: Identifiable {
|
||||
case share([Any])
|
||||
|
||||
// XXX: alert ids
|
||||
var id: Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@State private var modalType: ModalType?
|
||||
|
||||
private let versionString = Constants.Global.appVersionString
|
||||
|
||||
private let readmeURL = Constants.URLs.readme
|
||||
|
@ -51,24 +40,13 @@ struct AboutView: View {
|
|||
|
||||
private let privacyURL = Constants.URLs.privacyPolicy
|
||||
|
||||
private let alternativeToURL = Constants.URLs.alternativeTo
|
||||
|
||||
private let shareMessage = L10n.Global.Messages.share
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
infoSubview
|
||||
githubSubview
|
||||
webSubview
|
||||
shareSubview
|
||||
}.themeSecondaryView()
|
||||
.navigationTitle(L10n.About.title)
|
||||
.sheet(item: $modalType) {
|
||||
switch $0 {
|
||||
case .share(let items):
|
||||
ActivityView(activityItems: items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var infoSubview: some View {
|
||||
|
@ -116,28 +94,4 @@ struct AboutView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var shareSubview: some View {
|
||||
Section(
|
||||
header: Text(L10n.About.Sections.Share.header)
|
||||
) {
|
||||
Button(L10n.About.Items.ShareTwitter.caption, action: shareOnTwitter)
|
||||
Button(L10n.About.Items.ShareGeneric.caption, action: shareWithFriend)
|
||||
Button(Unlocalized.About.alternativeTo, action: shareAlternativeTo)
|
||||
}
|
||||
}
|
||||
|
||||
private func shareOnTwitter() {
|
||||
let url = Unlocalized.Social.twitterIntent(withMessage: shareMessage)
|
||||
URL.openURL(url)
|
||||
}
|
||||
|
||||
private func shareWithFriend() {
|
||||
let shareMessage = "\(shareMessage) \(Constants.URLs.website)"
|
||||
modalType = .share([shareMessage])
|
||||
}
|
||||
|
||||
private func shareAlternativeTo() {
|
||||
URL.openURL(alternativeToURL)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,6 @@
|
|||
import SwiftUI
|
||||
|
||||
struct MainView: View {
|
||||
init() {
|
||||
themeConfigureNavigationBarAppearance()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
OrganizerView()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//
|
||||
// OrganizerView+AddProfileMenu.swift
|
||||
// OrganizerView+AddMenu.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 4/2/22.
|
||||
// Created by Davide De Rosa on 4/18/22.
|
||||
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
|
@ -27,31 +27,21 @@ import SwiftUI
|
|||
import PassepartoutCore
|
||||
|
||||
extension OrganizerView {
|
||||
struct AddProfileMenu: View {
|
||||
struct Bindings {
|
||||
@Binding var modalType: ModalType?
|
||||
|
||||
@Binding var alertType: AlertType?
|
||||
|
||||
@Binding var isHostFileImporterPresented: Bool
|
||||
}
|
||||
|
||||
private let withImportedURLs: Bool
|
||||
|
||||
private let bindings: Bindings
|
||||
|
||||
init(
|
||||
withImportedURLs: Bool,
|
||||
bindings: Bindings
|
||||
) {
|
||||
self.withImportedURLs = withImportedURLs
|
||||
self.bindings = bindings
|
||||
struct AddMenu: View {
|
||||
@Binding private var modalType: ModalType?
|
||||
|
||||
@Binding private var isHostFileImporterPresented: Bool
|
||||
|
||||
init(modalType: Binding<ModalType?>, isHostFileImporterPresented: Binding<Bool>) {
|
||||
_modalType = modalType
|
||||
_isHostFileImporterPresented = isHostFileImporterPresented
|
||||
}
|
||||
|
||||
// FIXME: l10n, shorten menu captions
|
||||
var body: some View {
|
||||
Group {
|
||||
Menu {
|
||||
Button {
|
||||
bindings.modalType = .addProvider
|
||||
modalType = .addProvider
|
||||
} label: {
|
||||
Label(L10n.Organizer.Items.AddProvider.caption, systemImage: themeProviderImage)
|
||||
}
|
||||
|
@ -60,12 +50,12 @@ extension OrganizerView {
|
|||
} label: {
|
||||
Label(L10n.Organizer.Items.AddHost.caption, systemImage: themeHostImage)
|
||||
}
|
||||
if withImportedURLs {
|
||||
if let urls = importedURLs, !urls.isEmpty {
|
||||
Divider()
|
||||
importedURLs.map { urls in
|
||||
ForEach(urls, id: \.absoluteString, content: importedURLRow)
|
||||
}
|
||||
ForEach(urls, id: \.absoluteString, content: importedURLRow)
|
||||
}
|
||||
} label: {
|
||||
themeAddMenuImage.asSystemImage
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,33 +76,31 @@ extension OrganizerView {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OrganizerView.AddProfileMenu {
|
||||
private func presentAddProvider() {
|
||||
bindings.modalType = .addProvider
|
||||
}
|
||||
|
||||
private func presentAddHost(withURL url: URL, deletingURLOnSuccess: Bool) {
|
||||
bindings.modalType = .addHost(url, deletingURLOnSuccess)
|
||||
}
|
||||
|
||||
private func presentHostFileImporter() {
|
||||
|
||||
// XXX: iOS bug, hack around crappy bug when dismissing by swiping down
|
||||
//
|
||||
// https://stackoverflow.com/questions/66965471/swiftui-fileimporter-modifier-not-updating-binding-when-dismissed-by-tapping
|
||||
bindings.isHostFileImporterPresented = false
|
||||
Task {
|
||||
await Task.maybeWait(forMilliseconds: Constants.Delays.xxxPresentFileImporter)
|
||||
bindings.isHostFileImporterPresented = true
|
||||
private func presentAddProvider() {
|
||||
modalType = .addProvider
|
||||
}
|
||||
// isHostFileImporterPresented = true
|
||||
|
||||
// // use this to test hardcoded bundle file
|
||||
// let url = Bundle.main.url(forResource: "pia", withExtension: "ovpn")!
|
||||
// importedProfileName = "pia.ovpn"
|
||||
// modalType = .addHost(url, false)
|
||||
private func presentAddHost(withURL url: URL, deletingURLOnSuccess: Bool) {
|
||||
modalType = .addHost(url, deletingURLOnSuccess)
|
||||
}
|
||||
|
||||
private func presentHostFileImporter() {
|
||||
|
||||
// XXX: iOS bug, hack around crappy bug when dismissing by swiping down
|
||||
//
|
||||
// https://stackoverflow.com/questions/66965471/swiftui-fileimporter-modifier-not-updating-binding-when-dismissed-by-tapping
|
||||
isHostFileImporterPresented = false
|
||||
Task {
|
||||
await Task.maybeWait(forMilliseconds: Constants.Delays.xxxPresentFileImporter)
|
||||
isHostFileImporterPresented = true
|
||||
}
|
||||
// isHostFileImporterPresented = true
|
||||
|
||||
// // use this to test hardcoded bundle file
|
||||
// let url = Bundle.main.url(forResource: "pia", withExtension: "ovpn")!
|
||||
// importedProfileName = "pia.ovpn"
|
||||
// modalType = .addHost(url, false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ import SwiftUI
|
|||
import PassepartoutCore
|
||||
|
||||
extension OrganizerView {
|
||||
struct ProfilesSection: View {
|
||||
struct ProfilesList: View {
|
||||
@ObservedObject private var appManager: AppManager
|
||||
|
||||
@ObservedObject private var profileManager: ProfileManager
|
||||
|
@ -36,39 +36,32 @@ extension OrganizerView {
|
|||
|
||||
// just to observe changes in profiles eligibility
|
||||
@ObservedObject private var productManager: ProductManager
|
||||
|
||||
private let addProfileMenuBindings: AddProfileMenu.Bindings
|
||||
|
||||
@Binding private var alertType: AlertType?
|
||||
|
||||
@State private var isFirstLaunch = true
|
||||
|
||||
@State private var selectedProfileId: UUID?
|
||||
|
||||
init(addProfileMenuBindings: AddProfileMenu.Bindings) {
|
||||
init(alertType: Binding<AlertType?>) {
|
||||
appManager = .shared
|
||||
profileManager = .shared
|
||||
providerManager = .shared
|
||||
productManager = .shared
|
||||
self.addProfileMenuBindings = addProfileMenuBindings
|
||||
_alertType = alertType
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
debugChanges()
|
||||
return Section {
|
||||
ReloadingContent(
|
||||
observing: profileManager.headers,
|
||||
equality: {
|
||||
Set($0) == Set($1)
|
||||
}
|
||||
) {
|
||||
if !$0.isEmpty {
|
||||
ForEach($0.sorted(), content: navigationLink(forHeader:))
|
||||
.onAppear(perform: selectActiveProfile)
|
||||
} else {
|
||||
AddProfileMenu(
|
||||
withImportedURLs: false,
|
||||
bindings: addProfileMenuBindings
|
||||
)
|
||||
}
|
||||
return ReloadingContent(
|
||||
observing: profileManager.headers,
|
||||
equality: {
|
||||
Set($0) == Set($1)
|
||||
}
|
||||
) { headers in
|
||||
mainView(headers)
|
||||
if headers.isEmpty {
|
||||
emptyView
|
||||
}
|
||||
}.onAppear(perform: performMigrationsIfNeeded)
|
||||
|
||||
|
@ -80,6 +73,23 @@ extension OrganizerView {
|
|||
selectedProfileId = $0.id
|
||||
}
|
||||
}
|
||||
|
||||
private func mainView(_ headers: [Profile.Header]) -> some View {
|
||||
List {
|
||||
Section {
|
||||
ForEach(headers.sorted(), content: navigationLink(forHeader:))
|
||||
.onAppear(perform: selectActiveProfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: l10n
|
||||
private var emptyView: some View {
|
||||
VStack {
|
||||
Text("No profiles")
|
||||
.themeInformativeText()
|
||||
}
|
||||
}
|
||||
|
||||
private func navigationLink(forHeader header: Profile.Header) -> some View {
|
||||
NavigationLink(tag: header.id, selection: $selectedProfileId) {
|
||||
|
@ -95,7 +105,7 @@ extension OrganizerView {
|
|||
}
|
||||
}
|
||||
|
||||
extension OrganizerView.ProfilesSection {
|
||||
extension OrganizerView.ProfilesList {
|
||||
struct ActiveProfileHeaderRow: View {
|
||||
@ObservedObject private var currentVPNState: VPNManager.ObservableState
|
||||
|
||||
|
@ -121,7 +131,7 @@ extension OrganizerView.ProfilesSection {
|
|||
}
|
||||
}
|
||||
|
||||
extension OrganizerView.ProfilesSection {
|
||||
extension OrganizerView.ProfilesList {
|
||||
private func selectActiveProfile() {
|
||||
guard isFirstLaunch else {
|
||||
return
|
||||
|
@ -133,7 +143,7 @@ extension OrganizerView.ProfilesSection {
|
|||
// - an alert is active, as it would break navigation
|
||||
// - on iPad, as it's already shown
|
||||
//
|
||||
if addProfileMenuBindings.alertType == nil,
|
||||
if alertType == nil,
|
||||
themeIdiom != .pad,
|
||||
let activeProfileId = profileManager.activeHeader?.id {
|
||||
|
||||
|
@ -146,7 +156,7 @@ extension OrganizerView.ProfilesSection {
|
|||
await appManager.doMigrations(profileManager)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func dismissSelectionIfDeleted(headers: [Profile.Header]) {
|
||||
if let selectedProfileId = selectedProfileId,
|
||||
!profileManager.isExistingProfile(withId: selectedProfileId) {
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// OrganizerView+SettingsMenu.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 4/18/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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PassepartoutCore
|
||||
|
||||
extension OrganizerView {
|
||||
struct SettingsMenu: View {
|
||||
@ObservedObject private var productManager: ProductManager
|
||||
|
||||
@Binding var modalType: ModalType?
|
||||
|
||||
@Binding var alertType: AlertType?
|
||||
|
||||
private var isEligibleForSiri: Bool {
|
||||
productManager.isEligible(forFeature: .siriShortcuts)
|
||||
}
|
||||
|
||||
private let redditURL = Constants.URLs.subreddit
|
||||
|
||||
private let alternativeToURL = Constants.URLs.alternativeTo
|
||||
|
||||
private let shareMessage = L10n.Global.Messages.share
|
||||
|
||||
private let appName = Unlocalized.appName
|
||||
|
||||
init(modalType: Binding<ModalType?>, alertType: Binding<AlertType?>) {
|
||||
productManager = .shared
|
||||
_modalType = modalType
|
||||
_alertType = alertType
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
Menu(L10n.Menu.Support.title) {
|
||||
supportMenu
|
||||
}
|
||||
// FIXME: l10n, refactor string id to "menu.share.title"
|
||||
Menu(L10n.About.Sections.Share.header) {
|
||||
shareMenu
|
||||
}
|
||||
Divider()
|
||||
shortcutsButton
|
||||
aboutButton
|
||||
// RemoveVPNSection()
|
||||
// betaSection
|
||||
} label: {
|
||||
themeSettingsMenuImage.asSystemImage
|
||||
}
|
||||
}
|
||||
|
||||
private var shortcutsButton: some View {
|
||||
Button {
|
||||
presentShortcutsOrPaywall()
|
||||
} label: {
|
||||
Label(L10n.Organizer.Items.SiriShortcuts.caption, systemImage: themeShortcutsImage)
|
||||
}
|
||||
}
|
||||
|
||||
private var supportMenu: some View {
|
||||
Group {
|
||||
Button {
|
||||
modalType = .donate
|
||||
} label: {
|
||||
Label(L10n.Organizer.Items.Donate.caption, systemImage: themeDonateImage)
|
||||
}.disabled(!productManager.canMakePayments())
|
||||
|
||||
Button {
|
||||
URL.openURL(redditURL)
|
||||
} label: {
|
||||
Label(L10n.Organizer.Items.JoinCommunity.caption, systemImage: themeRedditImage)
|
||||
}
|
||||
Button(action: submitReview) {
|
||||
Label(L10n.Organizer.Items.WriteReview.caption, systemImage: themeWriteReviewImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var shareMenu: some View {
|
||||
Group {
|
||||
Button(L10n.About.Items.ShareTwitter.caption, action: shareOnTwitter)
|
||||
Button(L10n.About.Items.ShareGeneric.caption, action: shareWithFriend)
|
||||
Button(Unlocalized.About.alternativeTo, action: shareAlternativeTo)
|
||||
}
|
||||
}
|
||||
|
||||
private var aboutButton: some View {
|
||||
Button(L10n.Organizer.Items.About.caption(appName)) {
|
||||
presentAbout()
|
||||
}
|
||||
}
|
||||
|
||||
private func presentShortcutsOrPaywall() {
|
||||
|
||||
// eligibility: enter Siri shortcuts or present paywall
|
||||
if isEligibleForSiri {
|
||||
modalType = .shortcuts
|
||||
} else {
|
||||
modalType = .presentPaywallShortcuts
|
||||
}
|
||||
}
|
||||
|
||||
private func shareOnTwitter() {
|
||||
let url = Unlocalized.Social.twitterIntent(withMessage: shareMessage)
|
||||
URL.openURL(url)
|
||||
}
|
||||
|
||||
private func shareWithFriend() {
|
||||
let shareMessage = "\(shareMessage) \(Constants.URLs.website)"
|
||||
modalType = .share([shareMessage])
|
||||
}
|
||||
|
||||
private func shareAlternativeTo() {
|
||||
URL.openURL(alternativeToURL)
|
||||
}
|
||||
|
||||
private func submitReview() {
|
||||
let reviewURL = Reviewer.urlForReview(withAppId: Constants.App.appStoreId)
|
||||
URL.openURL(reviewURL)
|
||||
}
|
||||
|
||||
private func presentAbout() {
|
||||
modalType = .about
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
//
|
||||
// OrganizerView+Scene.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 4/2/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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension OrganizerView {
|
||||
struct ShortcutsSection: View {
|
||||
@ObservedObject private var productManager: ProductManager
|
||||
|
||||
private var isEligibleForSiri: Bool {
|
||||
productManager.isEligible(forFeature: .siriShortcuts)
|
||||
}
|
||||
|
||||
@Binding private var modalType: ModalType?
|
||||
|
||||
init(modalType: Binding<ModalType?>) {
|
||||
productManager = .shared
|
||||
_modalType = modalType
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Section(
|
||||
header: Text(Unlocalized.Other.siri),
|
||||
footer: Text(L10n.Organizer.Sections.Siri.footer)
|
||||
) {
|
||||
// eligibility: enter Siri shortcuts or present paywall
|
||||
if isEligibleForSiri {
|
||||
NavigationLink {
|
||||
ShortcutsView()
|
||||
} label: {
|
||||
shortcutsRow
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
modalType = .presentPaywallShortcuts
|
||||
} label: {
|
||||
shortcutsRow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var shortcutsRow: some View {
|
||||
// Text(L10n.Organizer.Items.SiriShortcuts.caption)
|
||||
Label(L10n.Organizer.Items.SiriShortcuts.caption, systemImage: themeShortcutsImage)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
//
|
||||
// OrganizerView+VPN.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 4/2/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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import PassepartoutCore
|
||||
|
||||
extension OrganizerView {
|
||||
struct RemoveVPNSection: 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.Organizer.Items.Uninstall.caption, systemImage: themeDeleteImage)
|
||||
}.foregroundColor(themeErrorColor)
|
||||
.actionSheet(isPresented: $isAskingUninstallVPN) {
|
||||
ActionSheet(
|
||||
title: Text(L10n.Organizer.Alerts.UninstallVpn.message),
|
||||
message: nil,
|
||||
buttons: [
|
||||
.destructive(Text(L10n.Organizer.Items.Uninstall.caption), action: {
|
||||
Task {
|
||||
await vpnManager.uninstall()
|
||||
}
|
||||
}),
|
||||
.cancel(Text(L10n.Global.Strings.cancel))
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
import PassepartoutCore
|
||||
|
||||
struct OrganizerView: View {
|
||||
|
@ -33,6 +32,14 @@ struct OrganizerView: View {
|
|||
|
||||
case addHost(URL, Bool)
|
||||
|
||||
case shortcuts
|
||||
|
||||
case donate
|
||||
|
||||
case share([Any])
|
||||
|
||||
case about
|
||||
|
||||
case presentPaywallShortcuts
|
||||
|
||||
// XXX: alert ids
|
||||
|
@ -42,7 +49,15 @@ struct OrganizerView: View {
|
|||
|
||||
case .addHost: return 2
|
||||
|
||||
case .presentPaywallShortcuts: return 3
|
||||
case .shortcuts: return 3
|
||||
|
||||
case .donate: return 4
|
||||
|
||||
case .share: return 5
|
||||
|
||||
case .about: return 6
|
||||
|
||||
case .presentPaywallShortcuts: return 7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,18 +87,14 @@ struct OrganizerView: View {
|
|||
|
||||
@AppStorage(AppManager.DefaultKey.didHandleSubreddit.rawValue) var didHandleSubreddit = false
|
||||
|
||||
init() {
|
||||
appManager = .shared
|
||||
}
|
||||
|
||||
private let hostFileTypes = Constants.URLs.filetypes
|
||||
|
||||
private let redditURL = Constants.URLs.subreddit
|
||||
|
||||
private let appName = Unlocalized.appName
|
||||
|
||||
private let versionString = Constants.Global.appVersionString
|
||||
|
||||
init() {
|
||||
appManager = .shared
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
debugChanges()
|
||||
return ZStack {
|
||||
|
@ -91,22 +102,7 @@ struct OrganizerView: View {
|
|||
alertType: $alertType,
|
||||
didHandleSubreddit: $didHandleSubreddit
|
||||
)
|
||||
List {
|
||||
ProfilesSection(
|
||||
addProfileMenuBindings: .init(
|
||||
modalType: $modalType,
|
||||
alertType: $alertType,
|
||||
isHostFileImporterPresented: $isHostFileImporterPresented
|
||||
)
|
||||
)
|
||||
ShortcutsSection(
|
||||
modalType: $modalType
|
||||
)
|
||||
supportSection
|
||||
aboutSection
|
||||
RemoveVPNSection()
|
||||
// betaSection
|
||||
}
|
||||
ProfilesList(alertType: $alertType)
|
||||
}.navigationTitle(Unlocalized.appName)
|
||||
.toolbar(content: toolbar)
|
||||
.sheet(item: $modalType, content: presentedModal)
|
||||
|
@ -119,18 +115,20 @@ struct OrganizerView: View {
|
|||
).onOpenURL(perform: onOpenURL)
|
||||
}
|
||||
|
||||
private func toolbar() -> some View {
|
||||
Menu {
|
||||
AddProfileMenu(
|
||||
withImportedURLs: true,
|
||||
bindings: .init(
|
||||
modalType: $modalType,
|
||||
alertType: $alertType,
|
||||
isHostFileImporterPresented: $isHostFileImporterPresented
|
||||
)
|
||||
@ToolbarContentBuilder
|
||||
private func toolbar() -> some ToolbarContent {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
AddMenu(
|
||||
modalType: $modalType,
|
||||
isHostFileImporterPresented: $isHostFileImporterPresented
|
||||
)
|
||||
} label: {
|
||||
themeAddProfileImage.asSystemImage
|
||||
}
|
||||
ToolbarItemGroup(placement: .automatic) {
|
||||
SettingsMenu(
|
||||
modalType: $modalType,
|
||||
alertType: $alertType
|
||||
)
|
||||
EditButton() // FIXME: toolbars, this is not shown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,6 +159,24 @@ extension OrganizerView {
|
|||
)
|
||||
)
|
||||
}.themeGlobal()
|
||||
|
||||
case .shortcuts:
|
||||
NavigationView {
|
||||
ShortcutsView()
|
||||
}.themeGlobal()
|
||||
|
||||
case .donate:
|
||||
NavigationView {
|
||||
DonateView()
|
||||
}.themeGlobal()
|
||||
|
||||
case .share(let items):
|
||||
ActivityView(activityItems: items)
|
||||
|
||||
case .about:
|
||||
NavigationView {
|
||||
AboutView()
|
||||
}.themeGlobal()
|
||||
|
||||
case .presentPaywallShortcuts:
|
||||
NavigationView {
|
||||
|
@ -228,51 +244,10 @@ extension OrganizerView {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Minor sections
|
||||
|
||||
extension OrganizerView {
|
||||
private var supportSection: some View {
|
||||
Section(
|
||||
header: Text(L10n.Organizer.Sections.Support.header)
|
||||
) {
|
||||
NavigationLink {
|
||||
DonateView()
|
||||
} label: {
|
||||
Label(L10n.Organizer.Items.Donate.caption, systemImage: themeDonateImage)
|
||||
}.disabled(!SKPaymentQueue.canMakePayments())
|
||||
|
||||
Button {
|
||||
URL.openURL(redditURL)
|
||||
} label: {
|
||||
Label(L10n.Organizer.Items.JoinCommunity.caption, systemImage: themeRedditImage)
|
||||
}
|
||||
Button(action: submitReview) {
|
||||
Label(L10n.Organizer.Items.WriteReview.caption, systemImage: themeWriteReviewImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var aboutSection: some View {
|
||||
Section {
|
||||
NavigationLink {
|
||||
AboutView()
|
||||
} label: {
|
||||
Text(L10n.Organizer.Items.About.caption(appName))
|
||||
// .withTrailingText(versionString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
extension OrganizerView {
|
||||
private func presentSubscribeReddit() {
|
||||
alertType = .subscribeReddit
|
||||
}
|
||||
|
||||
private func submitReview() {
|
||||
let reviewURL = Reviewer.urlForReview(withAppId: Constants.App.appStoreId)
|
||||
URL.openURL(reviewURL)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,4 +124,38 @@ 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.Organizer.Items.Uninstall.caption, systemImage: themeDeleteImage)
|
||||
}.foregroundColor(themeErrorColor)
|
||||
.actionSheet(isPresented: $isAskingUninstallVPN) {
|
||||
ActionSheet(
|
||||
title: Text(L10n.Organizer.Alerts.UninstallVpn.message),
|
||||
message: nil,
|
||||
buttons: [
|
||||
.destructive(Text(L10n.Organizer.Items.Uninstall.caption), action: {
|
||||
Task {
|
||||
await vpnManager.uninstall()
|
||||
}
|
||||
}),
|
||||
.cancel(Text(L10n.Global.Strings.cancel))
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ extension ProfileView {
|
|||
var body: some View {
|
||||
Text(L10n.Profile.Welcome.message)
|
||||
.multilineTextAlignment(.center)
|
||||
.themeInformativeText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,7 @@ struct ProfileView: View {
|
|||
ExtraSection(currentProfile: profileManager.currentProfile)
|
||||
DiagnosticsSection(currentProfile: profileManager.currentProfile)
|
||||
removeProfileSection
|
||||
UninstallVPNSection()
|
||||
}
|
||||
|
||||
private var welcomeView: some View {
|
||||
|
@ -171,6 +172,27 @@ struct ProfileView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func confirmRemoveProfile() {
|
||||
withAnimation {
|
||||
removeProfile()
|
||||
}
|
||||
}
|
||||
|
||||
private func removeProfile() {
|
||||
guard profileManager.isExistingProfile(withId: header.id) else {
|
||||
assertionFailure("Deleting non-existent profile \(header.name)")
|
||||
return
|
||||
}
|
||||
IntentDispatcher.forgetProfile(withHeader: header)
|
||||
profileManager.removeProfiles(withIds: [header.id])
|
||||
|
||||
// XXX: iOS 14, NavigationLink removal via header removal in OrganizerView+Profiles doesn't pop
|
||||
if #available(iOS 15, *) {
|
||||
} else {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadProfileIfNeeded() {
|
||||
guard !isLoaded else {
|
||||
return
|
||||
|
@ -201,27 +223,6 @@ struct ProfileView: View {
|
|||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func confirmRemoveProfile() {
|
||||
withAnimation {
|
||||
removeProfile()
|
||||
}
|
||||
}
|
||||
|
||||
private func removeProfile() {
|
||||
guard profileManager.isExistingProfile(withId: header.id) else {
|
||||
assertionFailure("Deleting non-existent profile \(header.name)")
|
||||
return
|
||||
}
|
||||
IntentDispatcher.forgetProfile(withHeader: header)
|
||||
profileManager.removeProfiles(withIds: [header.id])
|
||||
|
||||
// XXX: iOS 14, NavigationLink removal via header removal in OrganizerView+Profiles doesn't pop
|
||||
if #available(iOS 15, *) {
|
||||
} else {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func presentPaywallTrustedNetworks() {
|
||||
modalType = .paywallTrustedNetworks
|
||||
|
|
|
@ -80,7 +80,10 @@ struct ShortcutsView: View {
|
|||
}
|
||||
|
||||
private var addSection: some View {
|
||||
Section {
|
||||
Section(
|
||||
// FIXME: l10n, string id
|
||||
footer: Text(L10n.Organizer.Sections.Siri.footer)
|
||||
) {
|
||||
NavigationLink(isActive: $isNavigationPresented) {
|
||||
AddView(
|
||||
pendingShortcut: delegatingPendingShortcut
|
||||
|
|
Loading…
Reference in New Issue