Skip to content

Commit

Permalink
Merge branch 'no-need-for-full-disk-permission'
Browse files Browse the repository at this point in the history
  • Loading branch information
lukepistrol committed Oct 4, 2024
2 parents d6173af + 78fe60e commit 9619f0d
Show file tree
Hide file tree
Showing 22 changed files with 731 additions and 145 deletions.
7 changes: 7 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ nesting:
excluded:
- DerivedData
- build

custom_rules:
sf_safe_symbol:
name: "Safe SFSymbol"
message: "Use `SFSafeSymbols` via `systemSymbol` parameters for type safety."
regex: "(Image\\(systemName:)|(NSImage\\(symbolName:)|(Label[^,]+?,\\s*systemImage:)|(UIApplicationShortcutIcon\\(systemImageName:)"
severity: warning
5 changes: 1 addition & 4 deletions Shared/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ enum Constants {

enum URLs {
static let timeMachinePreferencesPlist = URL(fileURLWithPath: "/Library/Preferences/com.apple.TimeMachine.plist")
static let settingsFullDiskAccess: URL = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!
static let timeMachineSystemSettings: URL = URL(string: "x-apple.systempreferences:com.apple.Time-Machine-Settings.extension")!
static let authorURL: URL = URL(string: "https://lukaspistrol.com")!

Expand All @@ -30,9 +29,7 @@ enum Constants {
static let githubSponsor: URL = URL(string: "https://github.com/sponsors/lukepistrol")!
static let buymeacoffee: URL = URL(string: "http://buymeacoffee.com/lukeeep")!

static var timeMachineApp: URL? {
URL(filePath: "/Applications/Time Machine.app")
}
static let timeMachineApp: URL? = URL(filePath: "/Applications/Time Machine.app")
}

enum Commands {
Expand Down
81 changes: 60 additions & 21 deletions TimeMachineStatus.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{
"originHash" : "90288fc062e15ae6d002014a29c6e503504dcbb9aecef39d93ed46eaaa54b1b9",
"pins" : [
{
"identity" : "sfsafesymbols",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols.git",
"state" : {
"revision" : "e2e28f4e56e1769c2ec3c61c9355fc64eb7a535a",
"version" : "5.3.0"
}
},
{
"identity" : "shellout",
"kind" : "remoteSourceControl",
Expand All @@ -23,8 +33,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed",
"version" : "1.5.3"
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
"version" : "1.6.1"
}
},
{
Expand All @@ -37,5 +47,5 @@
}
}
],
"version" : 2
"version" : 3
}
2 changes: 1 addition & 1 deletion TimeMachineStatus/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return windowController
}()

@MainActor let utility: TMUtility = .init()
@MainActor let utility: TMUtilityImpl = .init()

var updaterController: SPUStandardUpdaterController!

Expand Down
43 changes: 43 additions & 0 deletions TimeMachineStatus/Components/HideWindowControlsViewModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// HideWindowControlsViewModifier.swift
// TimeMachineStatus
//
// Created by Lukas Pistrol on 02.10.24.
//
// Copyright © 2024 Lukas Pistrol. All rights reserved.
//
// See LICENSE.md for license information.
//

import SwiftUI

private struct HideWindowControllsViewModifier: ViewModifier {

let types: [NSWindow.ButtonType]

func body(content: Content) -> some View {
content
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)) { notification in
guard let window = NSApplication.shared.windows.first(where: { $0.isKeyWindow }) else { return }
hideWindowControls(for: window)
}
}

private func hideWindowControls(for window: NSWindow) {
types.forEach {
window.standardWindowButton($0)?.isHidden = true
}
}
}

extension View {
func hideWindowControls(
_ types: [NSWindow.ButtonType] = [
.closeButton,
.miniaturizeButton,
.zoomButton
]
) -> some View {
modifier(HideWindowControllsViewModifier(types: types))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// PreferencesFileImporterViewModifier.swift
// TimeMachineStatus
//
// Created by Lukas Pistrol on 01.10.24.
//
// Copyright © 2024 Lukas Pistrol. All rights reserved.
//
// See LICENSE.md for license information.
//

import SwiftUI

struct PreferencesFileImporterViewModifier: ViewModifier {
@Binding var showPicker: Bool

func body(content: Content) -> some View {
content
.fileImporter(isPresented: $showPicker, allowedContentTypes: [.propertyList]) { _ in }
.fileDialogDefaultDirectory(Constants.URLs.timeMachinePreferencesPlist)
.fileDialogMessage(Text("dialog_label_select_file_\(lastPathComponent)"))
.fileDialogConfirmationLabel(Text("button_select"))
.fileDialogURLEnabled(#Predicate<URL> { url in
url.lastPathComponent == lastPathComponent
})
}

private var lastPathComponent: String { Constants.URLs.timeMachinePreferencesPlist.lastPathComponent }
}

extension View {
func preferencesFileImporter(_ showPicker: Binding<Bool>) -> some View {
modifier(PreferencesFileImporterViewModifier(showPicker: showPicker))
}
}
16 changes: 13 additions & 3 deletions TimeMachineStatus/Components/UserfacingErrorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import SwiftUI
struct UserfacingErrorView: View {
let error: UserfacingError?

@State private var openPreferencesFile: Bool = false

@ViewBuilder
var body: some View {
if let error {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 4) {
Symbols.exclamationMarkTriangleFill.image
Image(systemSymbol: .exclamationmarkTriangleFill)
.foregroundStyle(.red)
Text(error.title)
.frame(maxWidth: .infinity, alignment: .leading)
Expand All @@ -35,14 +37,22 @@ struct UserfacingErrorView: View {
}
if let action = error.action {
Divider()
Button(action.title) {
NSWorkspace.shared.open(action.url)
switch action {
case .link(let title, let url):
Button(title) {
NSWorkspace.shared.open(url)
}
case .grantAccess:
Button("button_grant_access") {
openPreferencesFile = true
}
}
}
}
.padding(8)
.card(.bar)
.padding()
.preferencesFileImporter($openPreferencesFile)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// VisualEffectBackgroundViewModifier.swift
// TimeMachineStatus
//
// Created by Lukas Pistrol on 02.10.24.
//
// Copyright © 2024 Lukas Pistrol. All rights reserved.
//
// See LICENSE.md for license information.
//

import SwiftUI

private struct VisualEffectBackgroundViewModifier: ViewModifier {

let material: NSVisualEffectView.Material
let state: NSVisualEffectView.State

func body(content: Content) -> some View {
content
.background {
_VisualEffectView(material: material, state: state)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
}
}
}

extension View {
@ViewBuilder
func backgroundVisualEffect(
_ material: NSVisualEffectView.Material,
state: NSVisualEffectView.State = .active
) -> some View {
if isPreview {
self
} else {
modifier(VisualEffectBackgroundViewModifier(material: material, state: state))
}
}
}

private struct _VisualEffectView: NSViewRepresentable {

let material: NSVisualEffectView.Material
let state: NSVisualEffectView.State

func makeNSView(context: Context) -> NSVisualEffectView {
let effectView = NSVisualEffectView()
effectView.material = material
effectView.state = state
return effectView
}

func updateNSView(_ nsView: NSVisualEffectView, context: Context) {}
}
25 changes: 11 additions & 14 deletions TimeMachineStatus/Error/UserfacingError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,37 @@
import SwiftUI

enum UserfacingError: Error {
case fullDiskPermissionDenied
case preferencesFilePermissionNotGranted
case debugError(error: Error)

var title: LocalizedStringKey {
switch self {
case .fullDiskPermissionDenied:
return "error_fulldiskpermissiondenied_title"
case .preferencesFilePermissionNotGranted:
return "button_grant_access"
case .debugError:
return "error_debug_title"
}
}

var failureReason: LocalizedStringKey? {
switch self {
case .fullDiskPermissionDenied:
return "error_fulldiskpermissiondenied_description"
case .preferencesFilePermissionNotGranted:
return "settings_item_preferences_file_permission"
case .debugError(let error):
return "error_debug_description\(String(describing: error))"
}
}

var action: Action? {
switch self {
case .fullDiskPermissionDenied:
Action(
title: "button_opensystemsettings",
url: Constants.URLs.settingsFullDiskAccess
)
default: nil
case .preferencesFilePermissionNotGranted:
return .grantAccess
default: return nil
}
}

struct Action {
let title: LocalizedStringKey
let url: URL
enum Action {
case link(title: LocalizedStringKey, url: URL)
case grantAccess
}
}
2 changes: 1 addition & 1 deletion TimeMachineStatus/Extensions/Bool+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SwiftUI

extension Bool {
var image: some View {
Image(systemName: self ? Symbols.checkmarkCircleFill() : Symbols.xmarkCircleFill())
Image(systemSymbol: self ? .checkmarkCircleFill : .xmarkCircleFill)
.foregroundStyle(self ? .green : .red)
}
}
14 changes: 14 additions & 0 deletions TimeMachineStatus/Extensions/Timer+Sendable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Timer+Sendable.swift
// TimeMachineStatus
//
// Created by Lukas Pistrol on 03.10.24.
//
// Copyright © 2024 Lukas Pistrol. All rights reserved.
//
// See LICENSE.md for license information.
//

import Foundation

extension Timer: @unchecked @retroactive Sendable {}
Loading

0 comments on commit 9619f0d

Please sign in to comment.