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 */; };
|
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 */; };
|
0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */; };
|
||||||
0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2CE27CADA6300C73B67 /* GenericVersionView.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 */; };
|
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 */; };
|
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 */; };
|
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
|
||||||
0E3B7FCD27E47B3700C66F13 /* AddHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView.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 */; };
|
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 */; };
|
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 */; };
|
0E44689627B051C300A14CE4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689527B051C300A14CE4 /* ProfileView.swift */; };
|
||||||
0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689B27B11B5300A14CE4 /* AboutView.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 */; };
|
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 */; };
|
0ED89C2027DE423B008B36D6 /* ShortcutsView+ConnectTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */; };
|
||||||
0ED89C2527DE45A3008B36D6 /* ProfileHeaderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */; };
|
0ED89C2527DE45A3008B36D6 /* ProfileHeaderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */; };
|
||||||
0EDE02C227F61C79000FBE3C /* EditableTextList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE02C127F61C79000FBE3C /* EditableTextList.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 */; };
|
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */; };
|
||||||
0EED0BB92733CEDA00C9FC68 /* PassepartoutCore in Frameworks */ = {isa = PBXBuildFile; productRef = 0EED0BB82733CEDA00C9FC68 /* PassepartoutCore */; };
|
0EED0BB92733CEDA00C9FC68 /* PassepartoutCore in Frameworks */ = {isa = PBXBuildFile; productRef = 0EED0BB82733CEDA00C9FC68 /* PassepartoutCore */; };
|
||||||
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF527DD0211007EB181 /* PaywallView.swift */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentActivity.swift; sourceTree = "<group>"; };
|
||||||
|
@ -391,11 +389,10 @@
|
||||||
0EB34BC927C6A70200B126DA /* OnDemandView.swift */,
|
0EB34BC927C6A70200B126DA /* OnDemandView.swift */,
|
||||||
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */,
|
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */,
|
||||||
0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */,
|
0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */,
|
||||||
0E34AC7D27F849050042F2AB /* OrganizerView+AddProfileMenu.swift */,
|
0E3CD47E280DA14B007075C0 /* OrganizerView+AddMenu.swift */,
|
||||||
0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */,
|
0E34AC7B27F845510042F2AB /* OrganizerView+Profiles.swift */,
|
||||||
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */,
|
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */,
|
||||||
0E34AC7927F8431D0042F2AB /* OrganizerView+Shortcuts.swift */,
|
0EE11CD1280D8317003BE431 /* OrganizerView+SettingsMenu.swift */,
|
||||||
0E34AC7527F83FE20042F2AB /* OrganizerView+VPN.swift */,
|
|
||||||
0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */,
|
0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */,
|
||||||
0E44689527B051C300A14CE4 /* ProfileView.swift */,
|
0E44689527B051C300A14CE4 /* ProfileView.swift */,
|
||||||
0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */,
|
0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */,
|
||||||
|
@ -937,8 +934,6 @@
|
||||||
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */,
|
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */,
|
||||||
0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */,
|
0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */,
|
||||||
0E92D7C627F103300033CB7B /* ProfileView+Configuration.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 */,
|
0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Identifiable.swift in Sources */,
|
||||||
0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */,
|
0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */,
|
||||||
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */,
|
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */,
|
||||||
|
@ -964,6 +959,7 @@
|
||||||
0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */,
|
0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */,
|
||||||
0E9C233327F47E95007D5FC7 /* IntentDispatcher+Activities.swift in Sources */,
|
0E9C233327F47E95007D5FC7 /* IntentDispatcher+Activities.swift in Sources */,
|
||||||
0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */,
|
0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */,
|
||||||
|
0E3CD47F280DA14B007075C0 /* OrganizerView+AddMenu.swift in Sources */,
|
||||||
0EB17EAA27D226C900D473B5 /* Constants+Extensions.swift in Sources */,
|
0EB17EAA27D226C900D473B5 /* Constants+Extensions.swift in Sources */,
|
||||||
0E53E63727E34FE2001D4902 /* AppContext.swift in Sources */,
|
0E53E63727E34FE2001D4902 /* AppContext.swift in Sources */,
|
||||||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
||||||
|
@ -986,7 +982,6 @@
|
||||||
0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */,
|
0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */,
|
||||||
0E71ACEF27C106B500F85C4B /* ProviderPresetView.swift in Sources */,
|
0E71ACEF27C106B500F85C4B /* ProviderPresetView.swift in Sources */,
|
||||||
0E0AD49027BD53CB00FBB520 /* ProfileView+Welcome.swift in Sources */,
|
0E0AD49027BD53CB00FBB520 /* ProfileView+Welcome.swift in Sources */,
|
||||||
0E34AC7627F83FE20042F2AB /* OrganizerView+VPN.swift in Sources */,
|
|
||||||
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */,
|
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */,
|
||||||
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */,
|
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */,
|
||||||
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */,
|
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */,
|
||||||
|
@ -1001,6 +996,7 @@
|
||||||
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
||||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */,
|
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */,
|
||||||
0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */,
|
0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */,
|
||||||
|
0EE11CD2280D8317003BE431 /* OrganizerView+SettingsMenu.swift in Sources */,
|
||||||
0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */,
|
0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */,
|
||||||
0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */,
|
0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */,
|
||||||
0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */,
|
0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */,
|
||||||
|
|
|
@ -37,28 +37,6 @@ extension Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
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, *)
|
@available(iOS 14, *)
|
||||||
var themeIdiom: UIUserInterfaceIdiom {
|
var themeIdiom: UIUserInterfaceIdiom {
|
||||||
UIDevice.current.userInterfaceIdiom
|
UIDevice.current.userInterfaceIdiom
|
||||||
|
@ -78,7 +56,7 @@ extension View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func themeNavigationViewStyle() -> some View {
|
private func themeNavigationViewStyle() -> some View {
|
||||||
switch UIDevice.current.userInterfaceIdiom {
|
switch themeIdiom {
|
||||||
case .phone:
|
case .phone:
|
||||||
navigationViewStyle(.stack)
|
navigationViewStyle(.stack)
|
||||||
|
|
||||||
|
@ -87,15 +65,8 @@ extension View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func themePrimaryView() -> some View {
|
func themePrimaryView() -> some View {
|
||||||
switch UIDevice.current.userInterfaceIdiom {
|
navigationBarTitleDisplayMode(.large)
|
||||||
case .phone:
|
|
||||||
navigationBarTitleDisplayMode(.large)
|
|
||||||
|
|
||||||
default:
|
|
||||||
themeSecondaryView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func themeSecondaryView() -> some View {
|
func themeSecondaryView() -> some View {
|
||||||
|
@ -106,6 +77,11 @@ extension View {
|
||||||
lineLimit(1)
|
lineLimit(1)
|
||||||
.truncationMode(.middle)
|
.truncationMode(.middle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func themeInformativeText() -> some View {
|
||||||
|
font(.title)
|
||||||
|
.foregroundColor(themeSecondaryColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Colors
|
// MARK: Colors
|
||||||
|
@ -180,10 +156,14 @@ extension View {
|
||||||
"externaldrive.connected.to.line.below.fill"
|
"externaldrive.connected.to.line.below.fill"
|
||||||
}
|
}
|
||||||
|
|
||||||
var themeAddProfileImage: String {
|
var themeSettingsMenuImage: String {
|
||||||
|
"ellipsis.circle"
|
||||||
|
}
|
||||||
|
|
||||||
|
var themeAddMenuImage: String {
|
||||||
"plus"
|
"plus"
|
||||||
}
|
}
|
||||||
|
|
||||||
var themeCheckmarkImage: String {
|
var themeCheckmarkImage: String {
|
||||||
"checkmark"
|
"checkmark"
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,10 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
SKPaymentQueue.default().remove(self)
|
SKPaymentQueue.default().remove(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func canMakePayments() -> Bool {
|
||||||
|
SKPaymentQueue.canMakePayments()
|
||||||
|
}
|
||||||
|
|
||||||
func refreshProducts() {
|
func refreshProducts() {
|
||||||
let ids = LocalProduct.all
|
let ids = LocalProduct.all
|
||||||
guard !ids.isEmpty else {
|
guard !ids.isEmpty else {
|
||||||
|
|
|
@ -78,8 +78,6 @@
|
||||||
<array>
|
<array>
|
||||||
<string>arm64</string>
|
<string>arm64</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIStatusBarStyle</key>
|
|
||||||
<string>UIStatusBarStyleLightContent</string>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
@ -93,8 +91,6 @@
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportsDocumentBrowser</key>
|
<key>UISupportsDocumentBrowser</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<false/>
|
|
||||||
<key>com.algoritmico.Passepartout.config</key>
|
<key>com.algoritmico.Passepartout.config</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>appstore_id</key>
|
<key>appstore_id</key>
|
||||||
|
|
|
@ -28,7 +28,7 @@ import TunnelKitManager
|
||||||
import TunnelKitOpenVPN
|
import TunnelKitOpenVPN
|
||||||
import TunnelKitWireGuard
|
import TunnelKitWireGuard
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import PassepartoutUtils
|
import PassepartoutCore
|
||||||
|
|
||||||
extension VPNStatus {
|
extension VPNStatus {
|
||||||
var localizedDescription: String {
|
var localizedDescription: String {
|
||||||
|
|
|
@ -26,17 +26,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AboutView: View {
|
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 versionString = Constants.Global.appVersionString
|
||||||
|
|
||||||
private let readmeURL = Constants.URLs.readme
|
private let readmeURL = Constants.URLs.readme
|
||||||
|
@ -51,24 +40,13 @@ struct AboutView: View {
|
||||||
|
|
||||||
private let privacyURL = Constants.URLs.privacyPolicy
|
private let privacyURL = Constants.URLs.privacyPolicy
|
||||||
|
|
||||||
private let alternativeToURL = Constants.URLs.alternativeTo
|
|
||||||
|
|
||||||
private let shareMessage = L10n.Global.Messages.share
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
infoSubview
|
infoSubview
|
||||||
githubSubview
|
githubSubview
|
||||||
webSubview
|
webSubview
|
||||||
shareSubview
|
|
||||||
}.themeSecondaryView()
|
}.themeSecondaryView()
|
||||||
.navigationTitle(L10n.About.title)
|
.navigationTitle(L10n.About.title)
|
||||||
.sheet(item: $modalType) {
|
|
||||||
switch $0 {
|
|
||||||
case .share(let items):
|
|
||||||
ActivityView(activityItems: items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var infoSubview: some View {
|
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
|
import SwiftUI
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
init() {
|
|
||||||
themeConfigureNavigationBarAppearance()
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
OrganizerView()
|
OrganizerView()
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//
|
//
|
||||||
// OrganizerView+AddProfileMenu.swift
|
// OrganizerView+AddMenu.swift
|
||||||
// Passepartout
|
// 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.
|
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||||
//
|
//
|
||||||
// https://github.com/passepartoutvpn
|
// https://github.com/passepartoutvpn
|
||||||
|
@ -27,31 +27,21 @@ import SwiftUI
|
||||||
import PassepartoutCore
|
import PassepartoutCore
|
||||||
|
|
||||||
extension OrganizerView {
|
extension OrganizerView {
|
||||||
struct AddProfileMenu: View {
|
struct AddMenu: View {
|
||||||
struct Bindings {
|
@Binding private var modalType: ModalType?
|
||||||
@Binding var modalType: ModalType?
|
|
||||||
|
@Binding private var isHostFileImporterPresented: Bool
|
||||||
@Binding var alertType: AlertType?
|
|
||||||
|
init(modalType: Binding<ModalType?>, isHostFileImporterPresented: Binding<Bool>) {
|
||||||
@Binding var isHostFileImporterPresented: Bool
|
_modalType = modalType
|
||||||
}
|
_isHostFileImporterPresented = isHostFileImporterPresented
|
||||||
|
|
||||||
private let withImportedURLs: Bool
|
|
||||||
|
|
||||||
private let bindings: Bindings
|
|
||||||
|
|
||||||
init(
|
|
||||||
withImportedURLs: Bool,
|
|
||||||
bindings: Bindings
|
|
||||||
) {
|
|
||||||
self.withImportedURLs = withImportedURLs
|
|
||||||
self.bindings = bindings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: l10n, shorten menu captions
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Menu {
|
||||||
Button {
|
Button {
|
||||||
bindings.modalType = .addProvider
|
modalType = .addProvider
|
||||||
} label: {
|
} label: {
|
||||||
Label(L10n.Organizer.Items.AddProvider.caption, systemImage: themeProviderImage)
|
Label(L10n.Organizer.Items.AddProvider.caption, systemImage: themeProviderImage)
|
||||||
}
|
}
|
||||||
|
@ -60,12 +50,12 @@ extension OrganizerView {
|
||||||
} label: {
|
} label: {
|
||||||
Label(L10n.Organizer.Items.AddHost.caption, systemImage: themeHostImage)
|
Label(L10n.Organizer.Items.AddHost.caption, systemImage: themeHostImage)
|
||||||
}
|
}
|
||||||
if withImportedURLs {
|
if let urls = importedURLs, !urls.isEmpty {
|
||||||
Divider()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension OrganizerView.AddProfileMenu {
|
private func presentAddProvider() {
|
||||||
private func presentAddProvider() {
|
modalType = .addProvider
|
||||||
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
|
|
||||||
}
|
}
|
||||||
// isHostFileImporterPresented = true
|
|
||||||
|
|
||||||
// // use this to test hardcoded bundle file
|
private func presentAddHost(withURL url: URL, deletingURLOnSuccess: Bool) {
|
||||||
// let url = Bundle.main.url(forResource: "pia", withExtension: "ovpn")!
|
modalType = .addHost(url, deletingURLOnSuccess)
|
||||||
// importedProfileName = "pia.ovpn"
|
}
|
||||||
// modalType = .addHost(url, false)
|
|
||||||
|
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
|
import PassepartoutCore
|
||||||
|
|
||||||
extension OrganizerView {
|
extension OrganizerView {
|
||||||
struct ProfilesSection: View {
|
struct ProfilesList: View {
|
||||||
@ObservedObject private var appManager: AppManager
|
@ObservedObject private var appManager: AppManager
|
||||||
|
|
||||||
@ObservedObject private var profileManager: ProfileManager
|
@ObservedObject private var profileManager: ProfileManager
|
||||||
|
@ -36,39 +36,32 @@ extension OrganizerView {
|
||||||
|
|
||||||
// just to observe changes in profiles eligibility
|
// just to observe changes in profiles eligibility
|
||||||
@ObservedObject private var productManager: ProductManager
|
@ObservedObject private var productManager: ProductManager
|
||||||
|
|
||||||
private let addProfileMenuBindings: AddProfileMenu.Bindings
|
@Binding private var alertType: AlertType?
|
||||||
|
|
||||||
@State private var isFirstLaunch = true
|
@State private var isFirstLaunch = true
|
||||||
|
|
||||||
@State private var selectedProfileId: UUID?
|
@State private var selectedProfileId: UUID?
|
||||||
|
|
||||||
init(addProfileMenuBindings: AddProfileMenu.Bindings) {
|
init(alertType: Binding<AlertType?>) {
|
||||||
appManager = .shared
|
appManager = .shared
|
||||||
profileManager = .shared
|
profileManager = .shared
|
||||||
providerManager = .shared
|
providerManager = .shared
|
||||||
productManager = .shared
|
productManager = .shared
|
||||||
self.addProfileMenuBindings = addProfileMenuBindings
|
_alertType = alertType
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
debugChanges()
|
debugChanges()
|
||||||
return Section {
|
return ReloadingContent(
|
||||||
ReloadingContent(
|
observing: profileManager.headers,
|
||||||
observing: profileManager.headers,
|
equality: {
|
||||||
equality: {
|
Set($0) == Set($1)
|
||||||
Set($0) == Set($1)
|
}
|
||||||
}
|
) { headers in
|
||||||
) {
|
mainView(headers)
|
||||||
if !$0.isEmpty {
|
if headers.isEmpty {
|
||||||
ForEach($0.sorted(), content: navigationLink(forHeader:))
|
emptyView
|
||||||
.onAppear(perform: selectActiveProfile)
|
|
||||||
} else {
|
|
||||||
AddProfileMenu(
|
|
||||||
withImportedURLs: false,
|
|
||||||
bindings: addProfileMenuBindings
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.onAppear(perform: performMigrationsIfNeeded)
|
}.onAppear(perform: performMigrationsIfNeeded)
|
||||||
|
|
||||||
|
@ -80,6 +73,23 @@ extension OrganizerView {
|
||||||
selectedProfileId = $0.id
|
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 {
|
private func navigationLink(forHeader header: Profile.Header) -> some View {
|
||||||
NavigationLink(tag: header.id, selection: $selectedProfileId) {
|
NavigationLink(tag: header.id, selection: $selectedProfileId) {
|
||||||
|
@ -95,7 +105,7 @@ extension OrganizerView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OrganizerView.ProfilesSection {
|
extension OrganizerView.ProfilesList {
|
||||||
struct ActiveProfileHeaderRow: View {
|
struct ActiveProfileHeaderRow: View {
|
||||||
@ObservedObject private var currentVPNState: VPNManager.ObservableState
|
@ObservedObject private var currentVPNState: VPNManager.ObservableState
|
||||||
|
|
||||||
|
@ -121,7 +131,7 @@ extension OrganizerView.ProfilesSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OrganizerView.ProfilesSection {
|
extension OrganizerView.ProfilesList {
|
||||||
private func selectActiveProfile() {
|
private func selectActiveProfile() {
|
||||||
guard isFirstLaunch else {
|
guard isFirstLaunch else {
|
||||||
return
|
return
|
||||||
|
@ -133,7 +143,7 @@ extension OrganizerView.ProfilesSection {
|
||||||
// - an alert is active, as it would break navigation
|
// - an alert is active, as it would break navigation
|
||||||
// - on iPad, as it's already shown
|
// - on iPad, as it's already shown
|
||||||
//
|
//
|
||||||
if addProfileMenuBindings.alertType == nil,
|
if alertType == nil,
|
||||||
themeIdiom != .pad,
|
themeIdiom != .pad,
|
||||||
let activeProfileId = profileManager.activeHeader?.id {
|
let activeProfileId = profileManager.activeHeader?.id {
|
||||||
|
|
||||||
|
@ -146,7 +156,7 @@ extension OrganizerView.ProfilesSection {
|
||||||
await appManager.doMigrations(profileManager)
|
await appManager.doMigrations(profileManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissSelectionIfDeleted(headers: [Profile.Header]) {
|
private func dismissSelectionIfDeleted(headers: [Profile.Header]) {
|
||||||
if let selectedProfileId = selectedProfileId,
|
if let selectedProfileId = selectedProfileId,
|
||||||
!profileManager.isExistingProfile(withId: 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 SwiftUI
|
||||||
import StoreKit
|
|
||||||
import PassepartoutCore
|
import PassepartoutCore
|
||||||
|
|
||||||
struct OrganizerView: View {
|
struct OrganizerView: View {
|
||||||
|
@ -33,6 +32,14 @@ struct OrganizerView: View {
|
||||||
|
|
||||||
case addHost(URL, Bool)
|
case addHost(URL, Bool)
|
||||||
|
|
||||||
|
case shortcuts
|
||||||
|
|
||||||
|
case donate
|
||||||
|
|
||||||
|
case share([Any])
|
||||||
|
|
||||||
|
case about
|
||||||
|
|
||||||
case presentPaywallShortcuts
|
case presentPaywallShortcuts
|
||||||
|
|
||||||
// XXX: alert ids
|
// XXX: alert ids
|
||||||
|
@ -42,7 +49,15 @@ struct OrganizerView: View {
|
||||||
|
|
||||||
case .addHost: return 2
|
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
|
@AppStorage(AppManager.DefaultKey.didHandleSubreddit.rawValue) var didHandleSubreddit = false
|
||||||
|
|
||||||
init() {
|
|
||||||
appManager = .shared
|
|
||||||
}
|
|
||||||
|
|
||||||
private let hostFileTypes = Constants.URLs.filetypes
|
private let hostFileTypes = Constants.URLs.filetypes
|
||||||
|
|
||||||
private let redditURL = Constants.URLs.subreddit
|
private let redditURL = Constants.URLs.subreddit
|
||||||
|
|
||||||
private let appName = Unlocalized.appName
|
init() {
|
||||||
|
appManager = .shared
|
||||||
private let versionString = Constants.Global.appVersionString
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
debugChanges()
|
debugChanges()
|
||||||
return ZStack {
|
return ZStack {
|
||||||
|
@ -91,22 +102,7 @@ struct OrganizerView: View {
|
||||||
alertType: $alertType,
|
alertType: $alertType,
|
||||||
didHandleSubreddit: $didHandleSubreddit
|
didHandleSubreddit: $didHandleSubreddit
|
||||||
)
|
)
|
||||||
List {
|
ProfilesList(alertType: $alertType)
|
||||||
ProfilesSection(
|
|
||||||
addProfileMenuBindings: .init(
|
|
||||||
modalType: $modalType,
|
|
||||||
alertType: $alertType,
|
|
||||||
isHostFileImporterPresented: $isHostFileImporterPresented
|
|
||||||
)
|
|
||||||
)
|
|
||||||
ShortcutsSection(
|
|
||||||
modalType: $modalType
|
|
||||||
)
|
|
||||||
supportSection
|
|
||||||
aboutSection
|
|
||||||
RemoveVPNSection()
|
|
||||||
// betaSection
|
|
||||||
}
|
|
||||||
}.navigationTitle(Unlocalized.appName)
|
}.navigationTitle(Unlocalized.appName)
|
||||||
.toolbar(content: toolbar)
|
.toolbar(content: toolbar)
|
||||||
.sheet(item: $modalType, content: presentedModal)
|
.sheet(item: $modalType, content: presentedModal)
|
||||||
|
@ -119,18 +115,20 @@ struct OrganizerView: View {
|
||||||
).onOpenURL(perform: onOpenURL)
|
).onOpenURL(perform: onOpenURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func toolbar() -> some View {
|
@ToolbarContentBuilder
|
||||||
Menu {
|
private func toolbar() -> some ToolbarContent {
|
||||||
AddProfileMenu(
|
ToolbarItem(placement: .primaryAction) {
|
||||||
withImportedURLs: true,
|
AddMenu(
|
||||||
bindings: .init(
|
modalType: $modalType,
|
||||||
modalType: $modalType,
|
isHostFileImporterPresented: $isHostFileImporterPresented
|
||||||
alertType: $alertType,
|
|
||||||
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()
|
}.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:
|
case .presentPaywallShortcuts:
|
||||||
NavigationView {
|
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
|
// MARK: Actions
|
||||||
|
|
||||||
extension OrganizerView {
|
extension OrganizerView {
|
||||||
private func presentSubscribeReddit() {
|
private func presentSubscribeReddit() {
|
||||||
alertType = .subscribeReddit
|
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 {
|
var body: some View {
|
||||||
Text(L10n.Profile.Welcome.message)
|
Text(L10n.Profile.Welcome.message)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
.themeInformativeText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,7 @@ struct ProfileView: View {
|
||||||
ExtraSection(currentProfile: profileManager.currentProfile)
|
ExtraSection(currentProfile: profileManager.currentProfile)
|
||||||
DiagnosticsSection(currentProfile: profileManager.currentProfile)
|
DiagnosticsSection(currentProfile: profileManager.currentProfile)
|
||||||
removeProfileSection
|
removeProfileSection
|
||||||
|
UninstallVPNSection()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var welcomeView: some View {
|
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() {
|
private func loadProfileIfNeeded() {
|
||||||
guard !isLoaded else {
|
guard !isLoaded else {
|
||||||
return
|
return
|
||||||
|
@ -201,27 +223,6 @@ struct ProfileView: View {
|
||||||
presentationMode.wrappedValue.dismiss()
|
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() {
|
private func presentPaywallTrustedNetworks() {
|
||||||
modalType = .paywallTrustedNetworks
|
modalType = .paywallTrustedNetworks
|
||||||
|
|
|
@ -80,7 +80,10 @@ struct ShortcutsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var addSection: some View {
|
private var addSection: some View {
|
||||||
Section {
|
Section(
|
||||||
|
// FIXME: l10n, string id
|
||||||
|
footer: Text(L10n.Organizer.Sections.Siri.footer)
|
||||||
|
) {
|
||||||
NavigationLink(isActive: $isNavigationPresented) {
|
NavigationLink(isActive: $isNavigationPresented) {
|
||||||
AddView(
|
AddView(
|
||||||
pendingShortcut: delegatingPendingShortcut
|
pendingShortcut: delegatingPendingShortcut
|
||||||
|
|
Loading…
Reference in New Issue