Skip to content

Commit

Permalink
Merge branch 'release/0.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
mangerlahn committed Nov 27, 2023
2 parents 9e98e73 + 5a6e8bd commit d023d4f
Show file tree
Hide file tree
Showing 132 changed files with 5,351 additions and 743 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
# Unreleased Changes

# 0.10
#### New and Improved:
- Added update checking via Homebrew Cask, which should allow for many more updates to be found
- The app list is now sorted by recently updated apps. Sort by app name can be restored via the main menu: View > Sort By > Name
- Improved messages when no release notes are available
- Interface improvements for macOS Sonoma

- Language support for:
- Arabic (Thanks Hussain & Nas!)
- Traditional Chinese (Thanks Pan!)
- Filipino (Thanks Gean Paulo!)
- Polish (Thanks Konrad!)
- Portugiese (Brasilian) (Thanks Felipe!)
- Tweaks for many languages, including Czech, Italian and Swedish (Thanks Jiří, Francesco, Peeter and all the others!)

#### Bug Fixes:
- Fixed a crash when updating apps via context menu
- Fixed an UI crash when updating apps
- Fixed cases in which the update button would be hidden
- Attempt to fix app list being empty
- The Update All button now only updates apps within Latest (no other apps will be opened)
- Fixed loading of release notes for certain apps

# 0.9
#### New and Improved:
- Apps from the Mac App Store can be updated from within Latest
Expand Down
140 changes: 117 additions & 23 deletions Latest.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

123 changes: 77 additions & 46 deletions Latest/Interface/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ import Cocoa
class ReleaseNotesErrorViewController: NSViewController {

/// The textField holding the error title
@IBOutlet weak var titleTextField: NSTextField!
@IBOutlet private weak var titleTextField: NSTextField!

/// The textField holding the error description
@IBOutlet weak var descriptionTextField: NSTextField!
@IBOutlet private weak var descriptionTextField: NSTextField!

/// Updates the description of the error
func show(_ error: Error) {
self.descriptionTextField.stringValue = error.localizedDescription
if let localizedError = error as? LocalizedError, let failureReason = localizedError.failureReason {
titleTextField.stringValue = localizedError.localizedDescription
descriptionTextField.stringValue = failureReason
} else {
descriptionTextField.stringValue = error.localizedDescription
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ class ReleaseNotesLoadingViewController: NSViewController {
self.activityIndicator.startAnimation(nil)
}


// MARK: - Accessors

/// The top inset ensuring the loading content to appear centered.
var topInset: CGFloat = 0 {
didSet {
self.horizontalConstraint.constant = (self.topInset / 2)
}
}

}

extension ReleaseNotesLoadingViewController: ReleaseNotesContentProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,9 @@ class ReleaseNotesViewController: NSViewController {
self.app = nil

// Prepare for empty state
let description = NSLocalizedString("NoAppSelectedDescription", comment: "Description of release notes empty state")
let error = NSError(domain: "com.max-langer.addism", code: 1000, userInfo: [NSLocalizedDescriptionKey: description])
let error = LatestError.custom(title: NSLocalizedString("NoAppSelectedTitle", comment: "Title of release notes empty state"),
description: NSLocalizedString("NoAppSelectedDescription", comment: "Description of release notes empty state"))
self.show(error)
self.content?.errorController?.titleTextField.stringValue = NSLocalizedString("NoAppSelectedTitle", comment: "Title of release notes empty state")

self.appInfoBackgroundView.isHidden = true
}
Expand Down Expand Up @@ -284,7 +283,6 @@ class ReleaseNotesViewController: NSViewController {
private func updateInsets() {
let inset = self.appInfoBackgroundView.frame.size.height
self.content?.textController?.updateInsets(with: inset)
self.content?.loadingController?.topInset = inset
}

/// Switches the content to error and displays the localized error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
var snapshot: AppListSnapshot = AppListSnapshot(withApps: [], filterQuery: nil) {
didSet {
self.updatePlaceholderVisibility()

// Update selected app
self.selectApp(at: self.selectedAppIndex)
}
}

Expand Down Expand Up @@ -170,7 +167,7 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
// Ensure the index is valid
guard row >= 0 && row < self.apps.count else { return -1 }
return self.snapshot.isSectionHeader(at: row) ? 27 : 60
return self.snapshot.isSectionHeader(at: row) ? 27 : 65
}

func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool {
Expand Down Expand Up @@ -277,6 +274,9 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
self.newSnapshot = nil
self.snapshot = snapshot
self.tableView.reloadData()

// Update selected app
self.ensureSelection()
}

@objc func updateTableViewAnimated() {
Expand All @@ -291,6 +291,9 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
self.newSnapshot = nil
self.updateTableView(with: oldSnapshot, with: self.snapshot)

// Update selected app
self.ensureSelection()

self.tableViewUpdateInProgress = false
self.updateTableViewAnimated()
}
Expand All @@ -310,6 +313,7 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
*/
func selectApp(at index: Int?) {
guard let index = index, index >= 0, let app = self.snapshot.app(at: index) else {
self.selectedApp = nil
self.tableView.deselectAll(nil)
self.scrubber?.animator().selectedIndex = -1

Expand Down Expand Up @@ -339,7 +343,8 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
// MARK: - Menu Item Stuff

private func rowIndex(forMenuItem menuItem: NSMenuItem?) -> Int {
return menuItem?.representedObject as? Int ?? self.tableView.selectedRow
guard let app = menuItem?.representedObject as? App, let index = self.snapshot.index(of: app) else { return self.tableView.selectedRow }
return index
}

/// Open a single app
Expand Down Expand Up @@ -399,7 +404,7 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
let row = self.tableView.clickedRow

guard row != -1, !self.snapshot.isSectionHeader(at: row) else { return }
menu.items.forEach({ $0.representedObject = row })
menu.items.forEach({ $0.representedObject = self.snapshot.app(at: row) })
}


Expand All @@ -411,10 +416,13 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable

// MARK: - Actions

/// Updates the app and a given index
/// Updates the app at the given index.
private func updateApp(atIndex index: Int) {
guard let app = self.app(at: index) else { return }

// Delay update to improve animations
DispatchQueue.main.async {
self.app(at: index)?.performUpdate()
app.performUpdate()
}
}

Expand Down Expand Up @@ -477,6 +485,10 @@ class UpdateTableViewController: NSViewController, NSMenuItemValidation, NSTable
}
}

private func ensureSelection() {
self.selectApp(at: self.selectedAppIndex)
}

/// Animates changes made to the apps list
private func updateTableView(with oldSnapshot: AppListSnapshot, with newSnapshot: AppListSnapshot) {
let oldValue = oldSnapshot.entries
Expand Down
34 changes: 21 additions & 13 deletions Latest/Interface/Update Table View/Views/UpdateCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class UpdateCell: NSTableCellView {
/// The constraint defining the leading inset of the content.
@IBOutlet private weak var leadingConstraint: NSLayoutConstraint!

/// Label displaying the last modified/update date for the app.
@IBOutlet private weak var dateTextField: NSTextField!

/// The button handling the update of the app.
@IBOutlet private weak var updateButton: UpdateButton!

Expand Down Expand Up @@ -71,6 +74,16 @@ class UpdateCell: NSTableCellView {

// MARK: - Utilities

/// A date formatter for preparing the update date.
private lazy var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .none
dateFormatter.dateStyle = .short
dateFormatter.doesRelativeDateFormatting = true

return dateFormatter
}()

private func updateContents() {
guard let app = self.app, let versionInformation = app.localizedVersionInformation else { return }

Expand All @@ -80,25 +93,20 @@ class UpdateCell: NSTableCellView {
self.currentVersionTextField.stringValue = versionInformation.current
self.newVersionTextField.stringValue = versionInformation.new ?? ""
self.newVersionTextField.isHidden = !app.updateAvailable
self.dateTextField.stringValue = dateFormatter.string(from: app.updateDate)
}

private func updateTitle() {
self.nameTextField.attributedStringValue = self.app?.highlightedName(for: self.filterQuery) ?? NSAttributedString()
}

private func updateTextColors() {
if self.backgroundStyle == .emphasized {
self.nameTextField.textColor = .alternateSelectedControlTextColor
self.currentVersionTextField.textColor = .alternateSelectedControlTextColor
self.newVersionTextField.textColor = .alternateSelectedControlTextColor
} else {
// Tint the name if the app is not supported
let supported = self.app?.supported ?? false

self.nameTextField.textColor = (supported ? .labelColor : .tertiaryLabelColor)
self.currentVersionTextField.textColor = (supported ? .secondaryLabelColor : .tertiaryLabelColor)
self.newVersionTextField.textColor = (supported ? .secondaryLabelColor : .tertiaryLabelColor)
}

// Tint the name if the app is not supported
let supported = self.app?.supported ?? false

self.nameTextField.textColor = (self.backgroundStyle == .emphasized ? .alternateSelectedControlTextColor : (supported ? .labelColor : .tertiaryLabelColor))
self.currentVersionTextField.textColor = (supported ? .secondaryLabelColor : .tertiaryLabelColor)
self.newVersionTextField.textColor = (supported ? .secondaryLabelColor : .tertiaryLabelColor)
self.dateTextField.textColor = (supported ? .secondaryLabelColor : .tertiaryLabelColor)
}
}
3 changes: 2 additions & 1 deletion Latest/Interface/Utilities/DisplayLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class DisplayLink: NSObject {

#if os(macOS)
func displayLinkOutputCallback(_ displayLink: CVDisplayLink, _ inNow: UnsafePointer<CVTimeStamp>, _ inOutputTime: UnsafePointer<CVTimeStamp>, _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer<CVOptionFlags>, _ displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn {

guard let displayLinkContext else { return kCVReturnInvalidArgument }

unsafeBitCast(displayLinkContext, to: DisplayLink.self).displayTick()
return kCVReturnSuccess
}
Expand Down
27 changes: 17 additions & 10 deletions Latest/Interface/Views/UpdateButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,33 +175,40 @@ class UpdateButton: NSButton {
/// Updates the visibility of single views with the given state.
private var interfaceState: InterfaceState = .none
private func updateInterfaceVisibility(with state: InterfaceState) {
self.isHidden = (state == .none)

// Nothing to update
guard self.interfaceState != state || self.contentCell.contentType != state.contentType else {
return
}

self.isHidden = (state == .none)
self.interfaceState = state
self.contentCell.contentType = state.contentType

var title: String?
var image: NSImage?
switch state {
case .update:
self.title = NSLocalizedString("UpdateAction", comment: "Action to update a given app.").localizedUppercase
self.image = nil
title = NSLocalizedString("UpdateAction", comment: "Action to update a given app.")
case .open:
self.title = NSLocalizedString("OpenAction", comment: "Action to open a given app.").localizedUppercase
self.image = nil
title = NSLocalizedString("OpenAction", comment: "Action to open a given app.")
case .error:
self.title = ""
if #available(OSX 11.0, *) {
self.image = NSImage(systemSymbolName: "exclamationmark.triangle.fill", accessibilityDescription: NSLocalizedString("ErrorButtonAccessibilityTitle", comment: "Description of button that opens an error dialogue."))
image = NSImage(systemSymbolName: "exclamationmark.triangle.fill", accessibilityDescription: NSLocalizedString("ErrorButtonAccessibilityTitle", comment: "Description of button that opens an error dialogue."))
} else {
self.image = NSImage(named: "warning")!
image = NSImage(named: "warning")!
}
default:
self.title = ""
self.image = nil
()
}

// Beginning with macOS 14, the button text is no longer uppercase
if #available(macOS 14.0, *) {
self.title = title ?? ""
} else {
self.title = title?.localizedUppercase ?? ""
}
self.image = image
}


Expand Down
6 changes: 5 additions & 1 deletion Latest/Interface/Views/UpdateButtonCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,12 @@ class UpdateButtonCell: NSButtonCell {
// Adjust the styling of the button
string.addAttribute(.foregroundColor, value: Self.tintColor, range: range)
string.addAttribute(.font, value: NSFont.systemFont(ofSize: self.font!.pointSize - 1, weight: .medium), range: range)

// Slightly shift frame to account for adjusted font size
var adjustedFrame = frame
adjustedFrame.origin.y -= 1

return super.drawTitle(string, withFrame: frame, in: controlView)
return super.drawTitle(string, withFrame: adjustedFrame, in: controlView)
}

override func drawImage(_ image: NSImage, withFrame frame: NSRect, in controlView: NSView) {
Expand Down
33 changes: 30 additions & 3 deletions Latest/Interface/Window Controllers/MainWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class MainWindowController: NSWindowController, NSMenuItemValidation, NSMenuDele

switch action {
case #selector(updateAll(_:)):
return UpdateCheckCoordinator.shared.appProvider.updatableApps.count != 0
return hasUpdatesAvailable
case #selector(reload(_:)):
return self.reloadButton.isEnabled
case #selector(performFindPanelAction(_:)):
Expand All @@ -137,6 +137,11 @@ class MainWindowController: NSWindowController, NSMenuItemValidation, NSMenuDele

func menuNeedsUpdate(_ menu: NSMenu) {
menu.items.forEach { (menuItem) in
// Sort By menu constructed dynamically
if menuItem.identifier == NSUserInterfaceItemIdentifier(rawValue: "sortByMenu") {
menuItem.submenu?.items = sortByMenuItems
}

guard let action = menuItem.action else { return }

switch action {
Expand All @@ -149,8 +154,18 @@ class MainWindowController: NSWindowController, NSMenuItemValidation, NSMenuDele
default:
()
}
}
}
}

private var sortByMenuItems: [NSMenuItem] {
AppListSettings.SortOptions.allCases.map { order in
let item = NSMenuItem(title: order.displayName, action: #selector(changeSortOrder), keyEquivalent: "")
item.representedObject = order
item.state = AppListSettings.shared.sortOrder == order ? .on : .off

return item
}
}


// MARK: - Update Checker Progress Delegate
Expand Down Expand Up @@ -183,7 +198,7 @@ class MainWindowController: NSWindowController, NSMenuItemValidation, NSMenuDele
self.reloadButton.isEnabled = true
self.reloadTouchBarButton.isEnabled = true
self.progressIndicator.isHidden = true
self.updateAllButton.isEnabled = UpdateCheckCoordinator.shared.appProvider.updatableApps.count != 0
self.updateAllButton.isEnabled = hasUpdatesAvailable
}


Expand All @@ -200,6 +215,18 @@ class MainWindowController: NSWindowController, NSMenuItemValidation, NSMenuDele
@IBAction func toggleShowUnsupportedUpdates(_ sender: NSMenuItem?) {
AppListSettings.shared.showUnsupportedUpdates = !AppListSettings.shared.showUnsupportedUpdates
}

@IBAction func changeSortOrder(_ sender: NSMenuItem?) {
AppListSettings.shared.sortOrder = sender?.representedObject as! AppListSettings.SortOptions
}


// MARK: - Accessors

/// Whether there are any updatable apps.
private var hasUpdatesAvailable: Bool {
!UpdateCheckCoordinator.shared.appProvider.updatableApps.isEmpty
}


// MARK: - Private Methods
Expand Down
Loading

0 comments on commit d023d4f

Please sign in to comment.