mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-02-16 12:52:11 +00:00
parent
752dc6229f
commit
4124ff5cae
@ -11,6 +11,7 @@
|
|||||||
0E7E3D692B9345FD002BBDB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */; };
|
0E7E3D692B9345FD002BBDB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */; };
|
||||||
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */; };
|
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */; };
|
||||||
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; };
|
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; };
|
||||||
|
0EB08B982CA46F4900A02591 /* AppPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0EB08B962CA46F4900A02591 /* AppPlist.strings */; };
|
||||||
0EBE80DA2BF55C0E00E36A20 /* AppLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0EBE80D92BF55C0E00E36A20 /* AppLibrary */; };
|
0EBE80DA2BF55C0E00E36A20 /* AppLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0EBE80D92BF55C0E00E36A20 /* AppLibrary */; };
|
||||||
0EBE80DC2BF55C0E00E36A20 /* TunnelLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0EBE80DB2BF55C0E00E36A20 /* TunnelLibrary */; };
|
0EBE80DC2BF55C0E00E36A20 /* TunnelLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0EBE80DB2BF55C0E00E36A20 /* TunnelLibrary */; };
|
||||||
0EC066D12C7DC47600D88A94 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */; platformFilter = ios; };
|
0EC066D12C7DC47600D88A94 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */; platformFilter = ios; };
|
||||||
@ -55,6 +56,7 @@
|
|||||||
0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||||
0E8D852F2C328CA1005493DE /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
0E8D852F2C328CA1005493DE /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; };
|
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; };
|
||||||
|
0EB08B972CA46F4900A02591 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppPlist.strings; sourceTree = "<group>"; };
|
||||||
0EBE80DD2BF55C9100E36A20 /* Library */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Library; sourceTree = "<group>"; };
|
0EBE80DD2BF55C9100E36A20 /* Library */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Library; sourceTree = "<group>"; };
|
||||||
0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -129,6 +131,7 @@
|
|||||||
0ED1EFDA2C33059600CBD9BD /* App.plist */,
|
0ED1EFDA2C33059600CBD9BD /* App.plist */,
|
||||||
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */,
|
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */,
|
||||||
0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */,
|
0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */,
|
||||||
|
0EB08B962CA46F4900A02591 /* AppPlist.strings */,
|
||||||
0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */,
|
0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */,
|
||||||
0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */,
|
0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */,
|
||||||
0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */,
|
0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */,
|
||||||
@ -246,6 +249,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0E7E3D692B9345FD002BBDB4 /* Assets.xcassets in Resources */,
|
0E7E3D692B9345FD002BBDB4 /* Assets.xcassets in Resources */,
|
||||||
|
0EB08B982CA46F4900A02591 /* AppPlist.strings in Resources */,
|
||||||
0EC066D12C7DC47600D88A94 /* LaunchScreen.storyboard in Resources */,
|
0EC066D12C7DC47600D88A94 /* LaunchScreen.storyboard in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -319,6 +323,17 @@
|
|||||||
};
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
0EB08B962CA46F4900A02591 /* AppPlist.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
0EB08B972CA46F4900A02591 /* en */,
|
||||||
|
);
|
||||||
|
name = AppPlist.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
0E06D19C2B87629200176E1D /* Debug */ = {
|
0E06D19C2B87629200176E1D /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
@ -59,6 +59,7 @@ struct PassepartoutApp: App {
|
|||||||
)
|
)
|
||||||
AppLibrary.configure(with: context)
|
AppLibrary.configure(with: context)
|
||||||
}
|
}
|
||||||
|
.themeLockScreen()
|
||||||
.environmentObject(theme)
|
.environmentObject(theme)
|
||||||
.environmentObject(context.iapManager)
|
.environmentObject(context.iapManager)
|
||||||
.environmentObject(context.connectionObserver)
|
.environmentObject(context.connectionObserver)
|
||||||
|
3
Passepartout/App/en.lproj/AppPlist.strings
Normal file
3
Passepartout/App/en.lproj/AppPlist.strings
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"NSLocationWhenInUseUsageDescription" = "Access name of current Wi-Fi";
|
||||||
|
|
||||||
|
"NSFaceIDUsageDescription" = "Unlock app with Face ID";
|
@ -245,6 +245,8 @@ internal enum Strings {
|
|||||||
internal static let server = Strings.tr("Localizable", "global.server", fallback: "Server")
|
internal static let server = Strings.tr("Localizable", "global.server", fallback: "Server")
|
||||||
/// Servers
|
/// Servers
|
||||||
internal static let servers = Strings.tr("Localizable", "global.servers", fallback: "Servers")
|
internal static let servers = Strings.tr("Localizable", "global.servers", fallback: "Servers")
|
||||||
|
/// Settings
|
||||||
|
internal static let settings = Strings.tr("Localizable", "global.settings", fallback: "Settings")
|
||||||
/// Status
|
/// Status
|
||||||
internal static let status = Strings.tr("Localizable", "global.status", fallback: "Status")
|
internal static let status = Strings.tr("Localizable", "global.status", fallback: "Status")
|
||||||
/// Storage
|
/// Storage
|
||||||
@ -396,6 +398,8 @@ internal enum Strings {
|
|||||||
}
|
}
|
||||||
internal enum Views {
|
internal enum Views {
|
||||||
internal enum Advanced {
|
internal enum Advanced {
|
||||||
|
/// Lock app access
|
||||||
|
internal static let lockInBackground = Strings.tr("Localizable", "views.advanced.lock_in_background", fallback: "Lock app access")
|
||||||
/// Advanced
|
/// Advanced
|
||||||
internal static let title = Strings.tr("Localizable", "views.advanced.title", fallback: "Advanced")
|
internal static let title = Strings.tr("Localizable", "views.advanced.title", fallback: "Advanced")
|
||||||
internal enum Credits {
|
internal enum Credits {
|
||||||
@ -475,6 +479,10 @@ internal enum Strings {
|
|||||||
/// Make a donation
|
/// Make a donation
|
||||||
internal static let title = Strings.tr("Localizable", "views.donate.title", fallback: "Make a donation")
|
internal static let title = Strings.tr("Localizable", "views.donate.title", fallback: "Make a donation")
|
||||||
}
|
}
|
||||||
|
internal enum Lockable {
|
||||||
|
/// Passepartout is locked
|
||||||
|
internal static let message = Strings.tr("Localizable", "views.lockable.message", fallback: "Passepartout is locked")
|
||||||
|
}
|
||||||
internal enum Profile {
|
internal enum Profile {
|
||||||
internal enum ModuleList {
|
internal enum ModuleList {
|
||||||
internal enum Section {
|
internal enum Section {
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
"global.save" = "Save";
|
"global.save" = "Save";
|
||||||
"global.server" = "Server";
|
"global.server" = "Server";
|
||||||
"global.servers" = "Servers";
|
"global.servers" = "Servers";
|
||||||
|
"global.settings" = "Settings";
|
||||||
"global.status" = "Status";
|
"global.status" = "Status";
|
||||||
"global.storage" = "Storage";
|
"global.storage" = "Storage";
|
||||||
"global.subnet" = "Subnet";
|
"global.subnet" = "Subnet";
|
||||||
@ -120,6 +121,7 @@
|
|||||||
|
|
||||||
"views.advanced.title" = "Advanced";
|
"views.advanced.title" = "Advanced";
|
||||||
"views.advanced.sections.resources" = "Resources";
|
"views.advanced.sections.resources" = "Resources";
|
||||||
|
"views.advanced.lock_in_background" = "Lock app access";
|
||||||
|
|
||||||
"views.advanced.links.title" = "Links";
|
"views.advanced.links.title" = "Links";
|
||||||
"views.advanced.links.sections.support" = "Support";
|
"views.advanced.links.sections.support" = "Support";
|
||||||
@ -150,6 +152,8 @@
|
|||||||
"views.diagnostics.report_issue.title" = "Report issue";
|
"views.diagnostics.report_issue.title" = "Report issue";
|
||||||
"views.diagnostics.alerts.report_issue.email" = "The device is not configured to send e-mails.";
|
"views.diagnostics.alerts.report_issue.email" = "The device is not configured to send e-mails.";
|
||||||
|
|
||||||
|
"views.lockable.message" = "Passepartout is locked";
|
||||||
|
|
||||||
// MARK: - Module views
|
// MARK: - Module views
|
||||||
|
|
||||||
"modules.dns.servers.add" = "Add address";
|
"modules.dns.servers.add" = "Add address";
|
||||||
|
@ -29,6 +29,10 @@ import SwiftUI
|
|||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct AdvancedView: View {
|
struct AdvancedView: View {
|
||||||
|
|
||||||
|
@AppStorage(AppPreference.locksInBackground.key)
|
||||||
|
private var locksInBackground = false
|
||||||
|
|
||||||
let identifiers: Constants.Identifiers
|
let identifiers: Constants.Identifiers
|
||||||
|
|
||||||
@Binding
|
@Binding
|
||||||
@ -41,6 +45,10 @@ struct AdvancedView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension AdvancedView {
|
extension AdvancedView {
|
||||||
|
var lockInBackgroundToggle: some View {
|
||||||
|
Toggle(Strings.Views.Advanced.lockInBackground, isOn: $locksInBackground)
|
||||||
|
}
|
||||||
|
|
||||||
var donateLink: some View {
|
var donateLink: some View {
|
||||||
navLink(Strings.Views.Donate.title, to: .donate)
|
navLink(Strings.Views.Donate.title, to: .donate)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,11 @@ import SwiftUI
|
|||||||
extension AdvancedView {
|
extension AdvancedView {
|
||||||
var listView: some View {
|
var listView: some View {
|
||||||
List {
|
List {
|
||||||
|
Section {
|
||||||
|
lockInBackgroundToggle
|
||||||
|
} header: {
|
||||||
|
Text(Strings.Global.settings)
|
||||||
|
}
|
||||||
Section {
|
Section {
|
||||||
// TODO: donations
|
// TODO: donations
|
||||||
// donateLink
|
// donateLink
|
||||||
|
@ -30,11 +30,18 @@ import SwiftUI
|
|||||||
extension AdvancedView {
|
extension AdvancedView {
|
||||||
var listView: some View {
|
var listView: some View {
|
||||||
List(selection: $navigationRoute) {
|
List(selection: $navigationRoute) {
|
||||||
// TODO: donations
|
Section {
|
||||||
// donateLink
|
lockInBackgroundToggle
|
||||||
linksLink
|
} header: {
|
||||||
creditsLink
|
Text(Strings.Global.settings)
|
||||||
diagnosticsLink
|
}
|
||||||
|
Section {
|
||||||
|
// TODO: donations
|
||||||
|
// donateLink
|
||||||
|
linksLink
|
||||||
|
creditsLink
|
||||||
|
diagnosticsLink
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
Text(identifiers.versionString)
|
Text(identifiers.versionString)
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import CommonLibrary
|
||||||
|
import LocalAuthentication
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ struct ThemeBooleanModalModifier<Modal>: ViewModifier where Modal: View {
|
|||||||
modal()
|
modal()
|
||||||
.frame(minWidth: modalSize?.width, minHeight: modalSize?.height)
|
.frame(minWidth: modalSize?.width, minHeight: modalSize?.height)
|
||||||
.interactiveDismissDisabled(!isInteractive)
|
.interactiveDismissDisabled(!isInteractive)
|
||||||
|
.themeLockScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +93,7 @@ struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Iden
|
|||||||
modal($0)
|
modal($0)
|
||||||
.frame(minWidth: modalSize?.width, minHeight: modalSize?.height)
|
.frame(minWidth: modalSize?.width, minHeight: modalSize?.height)
|
||||||
.interactiveDismissDisabled(!isInteractive)
|
.interactiveDismissDisabled(!isInteractive)
|
||||||
|
.themeLockScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,6 +202,41 @@ struct ThemeHoverListRowModifier: ViewModifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ThemeLockScreenModifier: ViewModifier {
|
||||||
|
|
||||||
|
@AppStorage(AppPreference.locksInBackground.key)
|
||||||
|
private var locksInBackground = false
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
LockableView(
|
||||||
|
locksInBackground: $locksInBackground,
|
||||||
|
content: {
|
||||||
|
content
|
||||||
|
},
|
||||||
|
lockedContent: LogoView.init,
|
||||||
|
unlockBlock: Self.unlockScreenBlock
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func unlockScreenBlock() async -> Bool {
|
||||||
|
let context = LAContext()
|
||||||
|
let policy: LAPolicy = .deviceOwnerAuthentication
|
||||||
|
var error: NSError?
|
||||||
|
guard context.canEvaluatePolicy(policy, error: &error) else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let isAuthorized = try await context.evaluatePolicy(
|
||||||
|
policy,
|
||||||
|
localizedReason: Strings.Views.Lockable.message
|
||||||
|
)
|
||||||
|
return isAuthorized
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|
||||||
public enum ThemeAnimationCategory: CaseIterable {
|
public enum ThemeAnimationCategory: CaseIterable {
|
||||||
|
@ -60,6 +60,8 @@ public final class Theme: ObservableObject {
|
|||||||
|
|
||||||
var emptyMessageColor: Color = .secondary
|
var emptyMessageColor: Color = .secondary
|
||||||
|
|
||||||
|
var primaryColor = Color(red: 0.318, green: 0.365, blue: 0.443)
|
||||||
|
|
||||||
var activeColor = Color(red: .zero, green: Double(0xAA) / 255.0, blue: .zero)
|
var activeColor = Color(red: .zero, green: Double(0xAA) / 255.0, blue: .zero)
|
||||||
|
|
||||||
var inactiveColor: Color = .gray
|
var inactiveColor: Color = .gray
|
||||||
@ -72,6 +74,8 @@ public final class Theme: ObservableObject {
|
|||||||
|
|
||||||
var animationCategories: Set<ThemeAnimationCategory> = Set(ThemeAnimationCategory.allCases)
|
var animationCategories: Set<ThemeAnimationCategory> = Set(ThemeAnimationCategory.allCases)
|
||||||
|
|
||||||
|
var logoImage = "Logo"
|
||||||
|
|
||||||
var systemImage: (ImageName) -> String = {
|
var systemImage: (ImageName) -> String = {
|
||||||
switch $0 {
|
switch $0 {
|
||||||
case .add: return "plus"
|
case .add: return "plus"
|
||||||
@ -207,6 +211,10 @@ extension View {
|
|||||||
public func themeHoverListRow() -> some View {
|
public func themeHoverListRow() -> some View {
|
||||||
modifier(ThemeHoverListRowModifier())
|
modifier(ThemeHoverListRowModifier())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func themeLockScreen() -> some View {
|
||||||
|
modifier(ThemeLockScreenModifier())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// LogoView.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 3/20/23.
|
||||||
|
// Copyright (c) 2024 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
|
||||||
|
|
||||||
|
struct LogoView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var theme: Theme
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
theme.primaryColor
|
||||||
|
.ignoresSafeArea()
|
||||||
|
Image(theme.logoImage)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
LogoView()
|
||||||
|
.environmentObject(Theme())
|
||||||
|
}
|
@ -26,10 +26,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum AppPreference: String {
|
public enum AppPreference: String {
|
||||||
case profilesLayout
|
case locksInBackground
|
||||||
|
|
||||||
case logsPrivateData
|
case logsPrivateData
|
||||||
|
|
||||||
|
case profilesLayout
|
||||||
|
|
||||||
public var key: String {
|
public var key: String {
|
||||||
"App.\(rawValue)"
|
"App.\(rawValue)"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
//
|
||||||
|
// LockableView.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 3/20/23.
|
||||||
|
// Copyright (c) 2024 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
|
||||||
|
|
||||||
|
public struct LockableView<Content: View, LockedContent: View>: View {
|
||||||
|
|
||||||
|
@Environment(\.scenePhase)
|
||||||
|
private var scenePhase
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var locksInBackground: Bool
|
||||||
|
|
||||||
|
private let content: () -> Content
|
||||||
|
|
||||||
|
private let lockedContent: () -> LockedContent
|
||||||
|
|
||||||
|
private let unlockBlock: () async -> Bool
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
private var lock: Lock = .shared
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var state: Lock.State
|
||||||
|
|
||||||
|
public init(
|
||||||
|
locksInBackground: Binding<Bool>,
|
||||||
|
content: @escaping () -> Content,
|
||||||
|
lockedContent: @escaping () -> LockedContent,
|
||||||
|
unlockBlock: @escaping () async -> Bool
|
||||||
|
) {
|
||||||
|
_locksInBackground = locksInBackground
|
||||||
|
self.content = content
|
||||||
|
self.lockedContent = lockedContent
|
||||||
|
self.unlockBlock = unlockBlock
|
||||||
|
|
||||||
|
_state = .init {
|
||||||
|
Lock.shared.state
|
||||||
|
} set: {
|
||||||
|
Lock.shared.state = $0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
ZStack {
|
||||||
|
content()
|
||||||
|
if locksInBackground && state != .none {
|
||||||
|
lockedContent()
|
||||||
|
}
|
||||||
|
}.onChange(of: scenePhase, perform: onScenePhase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
private final class Lock: ObservableObject {
|
||||||
|
enum State {
|
||||||
|
case none
|
||||||
|
|
||||||
|
case covered
|
||||||
|
|
||||||
|
case locked
|
||||||
|
}
|
||||||
|
|
||||||
|
static let shared = Lock()
|
||||||
|
|
||||||
|
@Published var state: State = .locked
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
private extension LockableView {
|
||||||
|
func onScenePhase(_ scenePhase: ScenePhase) {
|
||||||
|
switch scenePhase {
|
||||||
|
case .active:
|
||||||
|
unlockIfNeeded()
|
||||||
|
|
||||||
|
case .inactive:
|
||||||
|
if state == .none {
|
||||||
|
state = .covered
|
||||||
|
}
|
||||||
|
|
||||||
|
case .background:
|
||||||
|
lockIfNeeded()
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lockIfNeeded() {
|
||||||
|
guard locksInBackground else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state = .locked
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockIfNeeded() {
|
||||||
|
guard locksInBackground else {
|
||||||
|
state = .none
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch state {
|
||||||
|
case .none:
|
||||||
|
break
|
||||||
|
|
||||||
|
case .covered:
|
||||||
|
state = .none
|
||||||
|
|
||||||
|
case .locked:
|
||||||
|
Task { @MainActor in
|
||||||
|
guard await unlockBlock() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state = .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user