Skip to content

Commit d264cd9

Browse files
Merge pull request #273 from root3nl/development
v3.0.3
2 parents 7137cc6 + 33a163b commit d264cd9

21 files changed

Lines changed: 143 additions & 45 deletions

src/Support.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@
825825
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
826826
CODE_SIGN_STYLE = Manual;
827827
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
828-
CURRENT_PROJECT_VERSION = 101;
828+
CURRENT_PROJECT_VERSION = 105;
829829
DEAD_CODE_STRIPPING = YES;
830830
DEVELOPMENT_TEAM = "";
831831
"DEVELOPMENT_TEAM[sdk=macosx*]" = 98LJ4XBGYK;
@@ -864,7 +864,7 @@
864864
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
865865
CODE_SIGN_STYLE = Manual;
866866
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
867-
CURRENT_PROJECT_VERSION = 101;
867+
CURRENT_PROJECT_VERSION = 105;
868868
DEAD_CODE_STRIPPING = YES;
869869
DEVELOPMENT_TEAM = "";
870870
"DEVELOPMENT_TEAM[sdk=macosx*]" = 98LJ4XBGYK;
@@ -901,7 +901,7 @@
901901
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
902902
CODE_SIGN_STYLE = Manual;
903903
COMBINE_HIDPI_IMAGES = YES;
904-
CURRENT_PROJECT_VERSION = 101;
904+
CURRENT_PROJECT_VERSION = 105;
905905
DEVELOPMENT_TEAM = "";
906906
"DEVELOPMENT_TEAM[sdk=macosx*]" = 98LJ4XBGYK;
907907
ENABLE_HARDENED_RUNTIME = YES;
@@ -934,7 +934,7 @@
934934
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
935935
CODE_SIGN_STYLE = Manual;
936936
COMBINE_HIDPI_IMAGES = YES;
937-
CURRENT_PROJECT_VERSION = 101;
937+
CURRENT_PROJECT_VERSION = 105;
938938
DEVELOPMENT_TEAM = "";
939939
"DEVELOPMENT_TEAM[sdk=macosx*]" = 98LJ4XBGYK;
940940
ENABLE_HARDENED_RUNTIME = YES;
@@ -1085,7 +1085,7 @@
10851085
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
10861086
CODE_SIGN_STYLE = Manual;
10871087
COMBINE_HIDPI_IMAGES = YES;
1088-
CURRENT_PROJECT_VERSION = 101;
1088+
CURRENT_PROJECT_VERSION = 105;
10891089
DEVELOPMENT_ASSET_PATHS = "\"Support/Preview Content\"";
10901090
DEVELOPMENT_TEAM = "";
10911091
"DEVELOPMENT_TEAM[sdk=macosx*]" = 98LJ4XBGYK;
@@ -1132,7 +1132,7 @@
11321132
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
11331133
CODE_SIGN_STYLE = Manual;
11341134
COMBINE_HIDPI_IMAGES = YES;
1135-
CURRENT_PROJECT_VERSION = 101;
1135+
CURRENT_PROJECT_VERSION = 105;
11361136
DEVELOPMENT_ASSET_PATHS = "\"Support/Preview Content\"";
11371137
DEVELOPMENT_TEAM = "";
11381138
"DEVELOPMENT_TEAM[sdk=macosx*]" = 98LJ4XBGYK;

src/Support/AppDelegate.swift

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,40 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
239239
}
240240
}
241241

242+
// Resolve which info item types are actually visible in the active layout.
243+
// The app has two rendering modes:
244+
// - Row-based layout uses `preferences.rows` and should only allow badges for item types present in those rows.
245+
// - Legacy layout uses `InfoItemOne...Six` and should only allow badges for slots in rows that are not hidden.
246+
// This keeps menu bar signaling aligned with what the user can actually see in the popover.
247+
func visibleInfoItemTypes() -> Set<String> {
248+
if !preferences.rows.isEmpty {
249+
return Set(
250+
preferences.rows
251+
.compactMap(\.items)
252+
.flatMap { $0.compactMap(\.type) }
253+
)
254+
}
255+
256+
var visibleItems = [String]()
257+
258+
if !preferences.hideFirstRowInfoItems {
259+
visibleItems.append(preferences.infoItemOne)
260+
visibleItems.append(preferences.infoItemTwo)
261+
}
262+
263+
if !preferences.hideSecondRowInfoItems {
264+
visibleItems.append(preferences.infoItemThree)
265+
visibleItems.append(preferences.infoItemFour)
266+
}
267+
268+
if !preferences.hideThirdRowInfoItems {
269+
visibleItems.append(preferences.infoItemFive)
270+
visibleItems.append(preferences.infoItemSix)
271+
}
272+
273+
return Set(visibleItems.filter { !$0.isEmpty })
274+
}
275+
242276
// MARK: - Set and update the menu bar icon
243277
@objc func setStatusBarIcon() {
244278

@@ -358,25 +392,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
358392

359393
// Set notification counter next to the menu bar icon if enabled. https://www.hackingwithswift.com/example-code/system/how-to-insert-images-into-an-attributed-string-with-nstextattachment
360394

361-
// Create array with configured info items. Disabled info items should not show a notification badge in the menu bar icon
362-
var infoItemsEnabled: [String] = [
363-
preferences.infoItemOne,
364-
preferences.infoItemTwo,
365-
preferences.infoItemThree,
366-
preferences.infoItemFour,
367-
preferences.infoItemFive,
368-
preferences.infoItemSix
369-
]
370-
371-
// Append all types from new structure
372-
if !preferences.rows.isEmpty {
373-
for row in preferences.rows {
374-
if let items = row.items {
375-
let allTypes = items.compactMap { $0.type }
376-
infoItemsEnabled.append(contentsOf: allTypes)
377-
}
378-
}
379-
}
395+
// Only item types visible in the currently rendered layout may drive a status bar badge.
396+
let infoItemsEnabled = visibleInfoItemTypes()
380397

381398
// Create array with extension alert booleans
382399
var extensionAlerts: [Bool] = []
@@ -1002,4 +1019,3 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
10021019
}
10031020
}
10041021
}
1005-

src/Support/Controllers/AppCatalogController.swift

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class AppCatalogController: ObservableObject {
3131

3232
// Current apps updating
3333
@Published var appsUpdating: [String] = []
34+
35+
// Indicates that App Catalog is checking for available updates
36+
@Published var checkingForUpdates: Bool = false
3437

3538
// Current apps in the queue
3639
@Published var appsQueued: [String] = []
@@ -41,10 +44,17 @@ class AppCatalogController: ObservableObject {
4144
// Array containing app details
4245
@Published var updateDetails: [InstalledAppItem] = []
4346

44-
// Calculate when next update schedule will run
47+
/// Calculates the relative date for the next automatic app update check.
48+
///
49+
/// This is only used for Catalog versions older than 1.9.0, where the
50+
/// configured update interval is still interpreted as a daily schedule.
51+
/// If the computed date is already in the past or within the next hour,
52+
/// the value is clamped to one hour from now because the exact
53+
/// LaunchDaemon execution time is not known.
4554
var nextUpdateDate: String {
4655
let fromDate = Date(timeIntervalSince1970: Double(lastUpdated))
47-
var toDate = fromDate.addingTimeInterval(Double(updateInterval) * 86400)
56+
let interval: Double = 86400
57+
var toDate = fromDate.addingTimeInterval(Double(updateInterval) * interval)
4858

4959
// If next update is not in the future, show within the next hour
5060
// If next update if within on hour, show within the next hour as we don't know exactly when the LaunchDaemon will run
@@ -59,12 +69,43 @@ class AppCatalogController: ObservableObject {
5969

6070
return relativeDate
6171
}
72+
73+
/// Returns the localized automatic update message shown in the UI.
74+
///
75+
/// Catalog 1.9.0 and newer checks for app updates every hour, so the text
76+
/// is a fixed localized sentence. Older versions still use the existing
77+
/// daily schedule messaging combined with `nextUpdateDate`.
78+
var automaticUpdateDescription: String {
79+
if catalogUsesHourlyUpdateInterval {
80+
return NSLocalizedString("APP_CATALOG_CHECKS_FOR_UPDATES_EVERY_HOUR", comment: "")
81+
} else {
82+
return "\(NSLocalizedString("APPS_WILL_BE_UPDATED_AUTOMATICALLY_DESCRIPTION", comment: "")) \(nextUpdateDate)"
83+
}
84+
}
85+
86+
/// Returns `true` when the installed Catalog app is version 1.9.0 or newer,
87+
/// which uses fixed hourly update checks instead of the previous daily schedule.
88+
private var catalogUsesHourlyUpdateInterval: Bool {
89+
let appURL = URL(fileURLWithPath: "/Applications/Catalog.app")
90+
91+
guard
92+
let bundle = Bundle(url: appURL),
93+
let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
94+
else {
95+
return false
96+
}
97+
98+
return version.compare("1.9.0", options: .numeric) != .orderedAscending
99+
}
62100

63101
// MARK: - Call Catalog Agent to check for updates
64102
func getAppUpdates() {
65103

66104
// Check available app updates
67105
logger.log("Checking app updates...")
106+
DispatchQueue.main.async {
107+
self.checkingForUpdates = true
108+
}
68109

69110
let command = "'/usr/local/bin/catalog --check-updates'"
70111

@@ -98,6 +139,10 @@ class AppCatalogController: ObservableObject {
98139

99140
// Decode app updates
100141
self.decodeAppUpdates()
142+
143+
DispatchQueue.main.async {
144+
self.checkingForUpdates = false
145+
}
101146
}
102147

103148
}

src/Support/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
<key>CFBundlePackageType</key>
1818
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>3.0.2</string>
20+
<string>3.0.3</string>
2121
<key>CFBundleVersion</key>
22-
<string>101</string>
22+
<string>105</string>
2323
<key>LSApplicationCategoryType</key>
2424
<string>public.app-category.utilities</string>
2525
<key>LSMinimumSystemVersion</key>

src/Support/Views/AppCatalog/AppUpdatesView.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ struct AppUpdatesView: View {
292292

293293
HStack(alignment: .top) {
294294

295-
Text("\(NSLocalizedString("APPS_WILL_BE_UPDATED_AUTOMATICALLY_DESCRIPTION", comment: "")) \(appCatalogController.nextUpdateDate)")
295+
Text(appCatalogController.automaticUpdateDescription)
296296
// .font(.system(.body, design: .rounded))
297297
// .foregroundStyle(.secondary)
298298

@@ -317,14 +317,39 @@ struct AppUpdatesView: View {
317317

318318
if appCatalogController.updateInterval > 0 {
319319

320-
Text("\(NSLocalizedString("APPS_WILL_BE_UPDATED_AUTOMATICALLY_DESCRIPTION", comment: "")) \(appCatalogController.nextUpdateDate)")
320+
Text(appCatalogController.automaticUpdateDescription)
321321
// Set frame to 250 to allow multiline text
322322
.frame(width: 250)
323323
.font(.system(.title3, design: .rounded))
324324
.multilineTextAlignment(.center)
325325
.foregroundStyle(.secondary)
326326

327327
}
328+
329+
if appCatalogController.checkingForUpdates {
330+
ProgressView()
331+
.frame(height: 35)
332+
} else {
333+
Button(action: {
334+
appCatalogController.getAppUpdates()
335+
}) {
336+
Text(NSLocalizedString("CHECK_FOR_UPDATES", comment: ""))
337+
.fontWeight(.bold)
338+
}
339+
.modify {
340+
if #available(macOS 26, *) {
341+
$0
342+
.buttonStyle(.glass)
343+
} else {
344+
$0
345+
.buttonStyle(.bordered)
346+
}
347+
}
348+
// .tint(color)
349+
.buttonBorderShape(.capsule)
350+
.controlSize(.extraLarge)
351+
.frame(height: 35)
352+
}
328353

329354
}
330355
.padding(.vertical, 40)
@@ -379,7 +404,7 @@ struct AppUpdatesView: View {
379404
appCatalogController.logger.debug("App \(bundleID, privacy: .public) added to update queue")
380405

381406
// Command to update app
382-
let command = "'/usr/local/bin/catalog --install \(bundleID) --update-action --support-app'"
407+
let command = "'/usr/local/bin/catalog --install \(bundleID) --update-action --force --support-app'"
383408

384409
// Remove Bundle ID from queued array
385410
await MainActor.run {

src/Support/Views/ButtonTemplateViews/InfoItem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ struct InfoItem: View {
115115
RemoveItemButtonView(configurationItem: configurationItem)
116116
}
117117
}
118-
.animation(.bouncy, value: hoverView)
118+
// .animation(.bouncy, value: hoverView)
119119
} else {
120120

121121
ZStack {

src/Support/Views/ButtonTemplateViews/Item.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ struct Item: View {
181181
RemoveItemButtonView(configurationItem: configurationItem)
182182
}
183183
}
184-
.animation(.bouncy, value: hoverView)
184+
// .animation(.bouncy, value: hoverView)
185185
} else {
186186

187187
ZStack {

src/Support/Views/ButtonTemplateViews/ItemCircle.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ struct ItemCircle: View {
9494
RemoveItemButtonView(configurationItem: configurationItem)
9595
}
9696
}
97-
.animation(.bouncy, value: hoverView)
97+
// .animation(.bouncy, value: hoverView)
9898
} else {
9999
ZStack {
100100
VStack {

src/Support/Views/ButtonTemplateViews/ItemDouble.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ struct ItemDouble: View {
139139
RemoveItemButtonView(configurationItem: configurationItem)
140140
}
141141
}
142-
.animation(.bouncy, value: hoverView)
142+
// .animation(.bouncy, value: hoverView)
143143
// FIXME: - Adjust when Jamf Connect Password Change can be triggered
144144
// https://docs.jamf.com/jamf-connect/2.9.1/documentation/Jamf_Connect_URL_Scheme.html#ID-00005c31
145145
.popover(isPresented: $showingAlert, arrowEdge: .leading) {

src/Support/Views/ButtonTemplateViews/ItemExtension.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ struct ItemExtension: View {
240240
RemoveItemButtonView(configurationItem: configurationItem)
241241
}
242242
}
243-
.animation(.bouncy, value: hoverView)
243+
// .animation(.bouncy, value: hoverView)
244244
} else {
245245

246246
ZStack {

0 commit comments

Comments
 (0)