Skip to content

Commit

Permalink
Report, Entitlements and Revalidating App Transactions (#171)
Browse files Browse the repository at this point in the history
* add copy report button, add revalidate app purchase btn, improve entitlement check

* bump version, fix safe brightness

* entitlement fixes
  • Loading branch information
niklasr22 authored Sep 18, 2024
1 parent eca813f commit cd4d224
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 75 deletions.
12 changes: 6 additions & 6 deletions BrightIntosh.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@
CODE_SIGN_ENTITLEMENTS = BrightIntosh/BrightIntosh_SE.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 51;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9K32WDUN2H;
Expand All @@ -419,7 +419,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.2.0;
PRODUCT_BUNDLE_IDENTIFIER = de.brightintosh.app;
PRODUCT_NAME = BrightIntosh;
SDKROOT = auto;
Expand All @@ -444,7 +444,7 @@
CODE_SIGN_ENTITLEMENTS = BrightIntosh/BrightIntosh_SE.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 51;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9K32WDUN2H;
Expand All @@ -471,7 +471,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.2.0;
PRODUCT_BUNDLE_IDENTIFIER = de.brightintosh.app;
PRODUCT_NAME = BrightIntosh;
SDKROOT = auto;
Expand Down Expand Up @@ -521,7 +521,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.2.0;
PRODUCT_BUNDLE_IDENTIFIER = de.brightintosh.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
Expand Down Expand Up @@ -572,7 +572,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 3.1.0;
MARKETING_VERSION = 3.2.0;
PRODUCT_BUNDLE_IDENTIFIER = de.brightintosh.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
Expand Down
7 changes: 3 additions & 4 deletions BrightIntosh/BrightnessManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,13 @@ class BrightnessManager {

func enableExtraBrightness() {
// Put brightness value into device specific bounds, as earlier versions allowed storing higher brightness values.
let safeBrightness = max(1.0, min(getDeviceMaxBrightness(), Settings.shared.brightness))
let safeBrightness = max(1.0, min(getDeviceMaxBrightness() - 0.01, Settings.shared.brightness))

if safeBrightness != Settings.shared.brightness {
print("Fixing brightness")
Settings.shared.brightness = max(1.0, min(getDeviceMaxBrightness(), Settings.shared.brightness))
} else {
self.brightnessTechnique?.enable()
Settings.shared.brightness = safeBrightness
}
self.brightnessTechnique?.enable()
}
}

Expand Down
17 changes: 13 additions & 4 deletions BrightIntosh/EntitlementHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@ class EntitlementHandler: ObservableObject {
return true
}

func isUnrestrictedUser() async -> Bool {
if await checkAppEntitlements() {
func isUnrestrictedUser(refresh: Bool = false) async -> Bool {
if !Settings.shared.ignoreAppTransaction && isUnrestrictedUser {
return true
}

if await checkAppEntitlements(refresh: refresh) {
DispatchQueue.main.async {
self.isUnrestrictedUser = true
}
Expand All @@ -76,13 +80,17 @@ class EntitlementHandler: ObservableObject {
return false
}

func checkAppEntitlements() async -> Bool {
func checkAppEntitlements(refresh: Bool = false) async -> Bool {
if Settings.shared.ignoreAppTransaction {
return false
}

do {
let shared = try await AppTransaction.shared
let shared = if refresh {
try await AppTransaction.refresh()
} else {
try await AppTransaction.shared
}
if case .verified(let appTransaction) = shared {
// Hard-code the major version number in which the app's business model changed.
let newBusinessModelMajorVersion = "3"
Expand All @@ -96,6 +104,7 @@ class EntitlementHandler: ObservableObject {
return true
}
}

} catch {
logger.error("Fetching app transaction failed")
}
Expand Down
56 changes: 56 additions & 0 deletions BrightIntosh/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,34 @@
}
}
},
"Generate and copy report" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bericht erstellen und kopieren"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Generar y copiar el informe"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Générer et copier le rapport"
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gerar e copiar o relatório"
}
}
}
},
"Hide" : {

},
Expand Down Expand Up @@ -889,6 +917,34 @@
}
}
},
"Information" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Information"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Información"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Informations"
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "Informação"
}
}
}
},
"Launch on login" : {
"localizations" : {
"de" : {
Expand Down
4 changes: 2 additions & 2 deletions BrightIntosh/StoreManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class StoreManager: NSObject, ObservableObject, SKProductsRequestDelegate, SKPay
private func complete(transaction: SKPaymentTransaction) {
SKPaymentQueue.default().finishTransaction(transaction)
Task {
_ = await EntitlementHandler.shared.checkAppEntitlements()
_ = await EntitlementHandler.shared.isUnrestrictedUser()
}
}

Expand All @@ -66,7 +66,7 @@ class StoreManager: NSObject, ObservableObject, SKProductsRequestDelegate, SKPay
private func restore(transaction: SKPaymentTransaction) {
SKPaymentQueue.default().finishTransaction(transaction)
Task {
_ = await EntitlementHandler.shared.checkAppEntitlements()
_ = await EntitlementHandler.shared.isUnrestrictedUser()
}
}

Expand Down
17 changes: 16 additions & 1 deletion BrightIntosh/UI/BrightIntoshStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,24 @@ struct BrightIntoshStoreView: View {
}
} else {
ProgressView()
Button(action: {
storeManager.fetchProducts()
}) {
Image(systemName: "arrow.counterclockwise")
}
Spacer()
}
RestorePurchasesButton()
RestorePurchasesButton(label: "Restore In-App Purchase", action: {
do {
try await AppStore.sync()
_ = await EntitlementHandler.shared.isUnrestrictedUser()
} catch {
print("Error while syncing")
}
})
RestorePurchasesButton(label: "Revalidate App Purchase", action: {
_ = await EntitlementHandler.shared.isUnrestrictedUser(refresh: true)
})
HStack {
Text("[Privacy Policy](https://brightintosh.de/app_privacy_policy_en.html)")
Text("[Terms](https://www.apple.com/legal/internet-services/itunes/dev/stdeula/)")
Expand Down
9 changes: 5 additions & 4 deletions BrightIntosh/UI/RestorePurchasesButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import SwiftUI
import StoreKit

struct RestorePurchasesButton: View {
public let label: String
public let action: () async -> ()
@State private var isRestoring = false

var body: some View {
Button(action: {
isRestoring = true
Task.detached {
defer { isRestoring = false }
try await AppStore.sync()
_ = await EntitlementHandler.shared.checkAppEntitlements()
await action()
}
}) {
Text("Restore Purchases")
Text(label)
.frame(maxWidth: 220.0)
}
.buttonStyle(BrightIntoshButtonStyle(backgroundColor: .gray))
Expand All @@ -31,5 +32,5 @@ struct RestorePurchasesButton: View {
}

#Preview {
RestorePurchasesButton()
RestorePurchasesButton(label: "Restore Purchases", action: {})
}
124 changes: 70 additions & 54 deletions BrightIntosh/UI/SettingsWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,70 +79,86 @@ struct BasicSettings: View {
@Environment(\.isUnrestrictedUser) private var isUnrestrictedUser: Bool

var body: some View {
VStack(alignment: HorizontalAlignment.leading) {
Section(header: Text("Brightness").bold()) {
Toggle("Increased brightness", isOn: $viewModel.brightIntoshActiveToggle)
Slider(value: $viewModel.brightnessSlider, in: 1.0...getDeviceMaxBrightness()) {
Text("Brightness")
ScrollView {
VStack(alignment: HorizontalAlignment.leading) {
Section(header: Text("Brightness").bold()) {
Toggle("Increased brightness", isOn: $viewModel.brightIntoshActiveToggle)
Slider(value: $viewModel.brightnessSlider, in: 1.0...getDeviceMaxBrightness()) {
Text("Brightness")
}
if isDeviceSupported() {
Toggle("Don't apply increased brightness to external XDR displays", isOn: $brightIntoshOnlyOnBuiltIn)
.onChange(of: brightIntoshOnlyOnBuiltIn) { value in
Settings.shared.brightIntoshOnlyOnBuiltIn = value
}
} else {
Label("Your device doesn't have a built-in XDR display. Increased brightness can only be enabled for external XDR displays.", systemImage: "exclamationmark.triangle.fill").foregroundColor(Color.yellow)
}
}
if isDeviceSupported() {
Toggle("Don't apply increased brightness to external XDR displays", isOn: $brightIntoshOnlyOnBuiltIn)
.onChange(of: brightIntoshOnlyOnBuiltIn) { value in
Settings.shared.brightIntoshOnlyOnBuiltIn = value
Section(header: Text("Automations").bold()) {
Toggle("Launch on login", isOn: $launchOnLogin)
.onChange(of: launchOnLogin) { value in
Settings.shared.launchAtLogin = value
}
} else {
Label("Your device doesn't have a built-in XDR display. Increased brightness can only be enabled for external XDR displays.", systemImage: "exclamationmark.triangle.fill").foregroundColor(Color.yellow)
}
}
Section(header: Text("Automations").bold()) {
Toggle("Launch on login", isOn: $launchOnLogin)
.onChange(of: launchOnLogin) { value in
Settings.shared.launchAtLogin = value
HStack {
Toggle("Disable when battery level drops under", isOn: $viewModel.batteryAutomationToggle)
TextField("Battery level threshold", value: $batteryLevelThreshold, format: .percent)
.onChange(of: batteryLevelThreshold) { value in
if !(0...100 ~= batteryLevelThreshold) {
batteryLevelThreshold = max(0, min(batteryLevelThreshold, 100))
} else {
Settings.shared.batteryAutomationThreshold = value
}
}
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 60)
.multilineTextAlignment(.center)
}
HStack {
Toggle("Disable when battery level drops under", isOn: $viewModel.batteryAutomationToggle)
TextField("Battery level threshold", value: $batteryLevelThreshold, format: .percent)
.onChange(of: batteryLevelThreshold) { value in
if !(0...100 ~= batteryLevelThreshold) {
batteryLevelThreshold = max(0, min(batteryLevelThreshold, 100))
} else {
Settings.shared.batteryAutomationThreshold = value
HStack {
Toggle("Disable after", isOn: $viewModel.timerAutomationToggle)
Picker(selection: $timerAutomationTimeout, label: EmptyView()) {
ForEach(Array(stride(from: 10, to: 51, by: 10)), id: \.self) { minutes in
Text("\(minutes) min").tag(minutes)
}
ForEach(Array(stride(from: 1, to: 5, by: 0.5)), id: \.self) { hours in
Text(String(format: "%.1f h", hours)).tag(Int(hours * 60))
}
}
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 60)
.multilineTextAlignment(.center)
}
HStack {
Toggle("Disable after", isOn: $viewModel.timerAutomationToggle)
Picker(selection: $timerAutomationTimeout, label: EmptyView()) {
ForEach(Array(stride(from: 10, to: 51, by: 10)), id: \.self) { minutes in
Text("\(minutes) min").tag(minutes)
}
ForEach(Array(stride(from: 1, to: 5, by: 0.5)), id: \.self) { hours in
Text(String(format: "%.1f h", hours)).tag(Int(hours * 60))
.onChange(of: timerAutomationTimeout) { value in
Settings.shared.timerAutomationTimeout = value
}
.frame(maxWidth: 80)
}
.onChange(of: timerAutomationTimeout) { value in
Settings.shared.timerAutomationTimeout = value
}
Section(header: Text("Shortcuts").bold()) {
Form {
KeyboardShortcuts.Recorder("Toggle increased brightness:", name: .toggleBrightIntosh)
KeyboardShortcuts.Recorder("Increase brightness:", name: .increaseBrightness)
KeyboardShortcuts.Recorder("Decrease brightness:", name: .decreaseBrightness)
}
.frame(maxWidth: 80)
}
}
Section(header: Text("Shortcuts").bold()) {
Form {
KeyboardShortcuts.Recorder("Toggle increased brightness:", name: .toggleBrightIntosh)
KeyboardShortcuts.Recorder("Increase brightness:", name: .increaseBrightness)
KeyboardShortcuts.Recorder("Decrease brightness:", name: .decreaseBrightness)
Section(header: Text("Information").bold()) {
VStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) {
Button(action: {
Task {
let report = await generateReport()
let pasteboard = NSPasteboard.general
pasteboard.declareTypes([.string], owner: nil)
pasteboard.setString(report, forType: .string)
}
}) {
Text("Generate and copy report")
}
}
}
}
}.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
).padding()
}.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
).padding()
}
}
}

Expand Down
Loading

0 comments on commit cd4d224

Please sign in to comment.