Skip to content

Commit

Permalink
Add updater
Browse files Browse the repository at this point in the history
  • Loading branch information
ChenglongMa committed Apr 18, 2024
1 parent 55d9316 commit bf53c3b
Show file tree
Hide file tree
Showing 25 changed files with 323 additions and 67 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ xcuserdata/

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
dist/
DerivedData/
*.moved-aside
*.pbxuser
Expand Down Expand Up @@ -88,4 +89,4 @@ fastlane/test_output
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/
.DS_Store
*.DS_Store
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"svg.preview.background": "editor"
}
27 changes: 12 additions & 15 deletions Waker/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@
}
}
},
"Check for Updates" : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "检查更新"
}
}
}
},
"Check for Updates…" : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
Expand Down Expand Up @@ -41,11 +52,9 @@
}
}
}
},
"Favorites" : {

},
"Feedback" : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
Expand Down Expand Up @@ -183,9 +192,6 @@
}
}
}
},
"Start Timer" : {

},
"Stop" : {
"extractionState" : "manual",
Expand All @@ -197,12 +203,6 @@
}
}
}
},
"Stop Timer" : {

},
"Timer is %@" : {

},
"to" : {
"localizations" : {
Expand All @@ -213,9 +213,6 @@
}
}
}
},
"Toggle Text" : {

},
"Wake Up Interval" : {
"localizations" : {
Expand Down
50 changes: 32 additions & 18 deletions Waker/Waker.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
6E0163D72BCF2C56007C264C /* CheckForUpdatesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0163D62BCF2C56007C264C /* CheckForUpdatesViewModel.swift */; };
6E0163D72BCF2C56007C264C /* CheckForUpdatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0163D62BCF2C56007C264C /* CheckForUpdatesView.swift */; };
6E0163DA2BCF3CDE007C264C /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 6E0163D92BCF3CDE007C264C /* Sparkle */; };
6E16AF9F2BC7FD0E006E913A /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E16AF9E2BC7FD0E006E913A /* Utils.swift */; };
6E1B96422BC780A100D417EA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1B96412BC780A100D417EA /* AppDelegate.swift */; };
Expand All @@ -21,7 +21,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
6E0163D62BCF2C56007C264C /* CheckForUpdatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckForUpdatesViewModel.swift; sourceTree = "<group>"; };
6E0163D62BCF2C56007C264C /* CheckForUpdatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckForUpdatesView.swift; sourceTree = "<group>"; };
6E0163DB2BCF4E36007C264C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
6E16AF9E2BC7FD0E006E913A /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
6E1B96412BC780A100D417EA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
6E1B96432BC7830600D417EA /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -67,10 +68,11 @@
6E41796C2BC75EDE00547152 /* Waker */ = {
isa = PBXGroup;
children = (
6E0163DB2BCF4E36007C264C /* Info.plist */,
6E41796D2BC75EDE00547152 /* WakerApp.swift */,
6EC7EBD12BCC869C002793B3 /* MenuBar.swift */,
6E41797E2BC76D3F00547152 /* WakerViewModel.swift */,
6E0163D62BCF2C56007C264C /* CheckForUpdatesViewModel.swift */,
6E0163D62BCF2C56007C264C /* CheckForUpdatesView.swift */,
6E1B96412BC780A100D417EA /* AppDelegate.swift */,
6E16AF9E2BC7FD0E006E913A /* Utils.swift */,
6E4179712BC75EDF00547152 /* Assets.xcassets */,
Expand Down Expand Up @@ -169,7 +171,7 @@
buildActionMask = 2147483647;
files = (
6E1B96422BC780A100D417EA /* AppDelegate.swift in Sources */,
6E0163D72BCF2C56007C264C /* CheckForUpdatesViewModel.swift in Sources */,
6E0163D72BCF2C56007C264C /* CheckForUpdatesView.swift in Sources */,
6E41797F2BC76D3F00547152 /* WakerViewModel.swift in Sources */,
6EC7EBD22BCC869C002793B3 /* MenuBar.swift in Sources */,
6E41796E2BC75EDE00547152 /* WakerApp.swift in Sources */,
Expand Down Expand Up @@ -305,26 +307,32 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Waker/Waker.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_IDENTITY = "Apple Development: [email protected] (6725BGVYMG)";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development: [email protected] (6725BGVYMG)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 0.9;
DEVELOPMENT_ASSET_PATHS = "\"Waker/Preview Content\"";
DEVELOPMENT_TEAM = CNYDYKNJDR;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = "";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Waker/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Waker;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Chenglong Ma. All rights reserved.";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.chenglongma.Waker;
MARKETING_VERSION = 0.9;
PRODUCT_BUNDLE_IDENTIFIER = com.chenglongma.waker;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
Expand All @@ -337,26 +345,32 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Waker/Waker.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_IDENTITY = "Apple Development: [email protected] (6725BGVYMG)";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development: [email protected] (6725BGVYMG)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 0.9;
DEVELOPMENT_ASSET_PATHS = "\"Waker/Preview Content\"";
DEVELOPMENT_TEAM = CNYDYKNJDR;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = "";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Waker/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Waker;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Chenglong Ma. All rights reserved.";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.chenglongma.Waker;
MARKETING_VERSION = 0.9;
PRODUCT_BUNDLE_IDENTIFIER = com.chenglongma.waker;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
Expand Down
91 changes: 91 additions & 0 deletions Waker/Waker/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,104 @@
// Waker
//
// Created by Chenglong Ma on 11/4/2024.
// Refer to: https://sparkle-project.org/documentation/gentle-reminders/
//

import Cocoa
import Sparkle
import UserNotifications
import SwiftUI

class AppDelegate: NSObject, NSApplicationDelegate {

func applicationWillFinishLaunching(_ notification: Notification) {
Utils.checkAccessibilityPermission()
}

// func applicationDidFinishLaunching(_ notification: Notification) {
// // Make the app run in the background
// NSApp.setActivationPolicy(.accessory)
// UNUserNotificationCenter.current().delegate = self
// }
}

class UpdaterDelegate: NSObject, UNUserNotificationCenterDelegate, SPUUpdaterDelegate, SPUStandardUserDriverDelegate {

// let updaterController: SPUStandardUpdaterController

// Request for permissions to publish notifications for update alerts
// This delegate method will be called when Sparkle schedules an update check in the future,
// which may be a good time to request for update permission. This will be after the user has allowed
// Sparkle to check for updates automatically. If you need to publish notifications for other reasons,
// then you may have a more ideal time to request for notification authorization unrelated to update checking.
func updater(_ updater: SPUUpdater, willScheduleUpdateCheckAfterDelay delay: TimeInterval) {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { granted, error in
// Examine granted outcome and error if desired...
if granted {
print("Granted to send notification")
} else {
print("Denied to send notification")
}
}
}

// Declares that we support gentle scheduled update reminders to Sparkle's standard user driver
var supportsGentleScheduledUpdateReminders: Bool {
return true
}

func standardUserDriverWillHandleShowingUpdate(_ handleShowingUpdate: Bool, forUpdate update: SUAppcastItem, state: SPUUserUpdateState) {
// When an update alert will be presented, place the app in the foreground
// We will do this for updates the user initiated themselves too for consistency
// When we later post a notification, the act of clicking a notification will also change the app
// to have a regular activation policy. For consistency, we should do this if the user
// does not click on the notification too.

// NSApp.setActivationPolicy(.regular)

if !state.userInitiated {
// And add a badge to the app's dock icon indicating one alert occurred
// NSApp.dockTile.badgeLabel = "1"

// Post a user notification
// For banner style notification alerts, this may only trigger when the app is currently inactive.
// For alert style notification alerts, this will trigger when the app is active or inactive.
do {
let content = UNMutableNotificationContent()
content.title = "A new update is available"
content.body = "Version \(update.displayVersionString) is now available"

let request = UNNotificationRequest(identifier: Constants.UPDATE_NOTIFICATION_IDENTIFIER, content: content, trigger: nil)

UNUserNotificationCenter.current().add(request)
}
}
}

func standardUserDriverDidReceiveUserAttention(forUpdate update: SUAppcastItem) {
// Clear the dock badge indicator for the update
// NSApp.dockTile.badgeLabel = "" // We have no dock icon

// Dismiss active update notifications if the user has given attention to the new update
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Constants.UPDATE_NOTIFICATION_IDENTIFIER])
}
//
// func standardUserDriverWillFinishUpdateSession() {
// // Put app back in background when the user session for the update finished.
// // We don't have a convenient reason for the user to easily activate the app now.
// // Note this assumes there's no other windows for the app to show
// NSApp.setActivationPolicy(.accessory)
// }
//
// func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
// if response.notification.request.identifier == UPDATE_NOTIFICATION_IDENTIFIER && response.actionIdentifier == UNNotificationDefaultActionIdentifier {
// // If the notificaton is clicked on, make sure we bring the update in focus
// // If the app is terminated while the notification is clicked on,
// // this will launch the application and perform a new update check.
// // This can be more likely to occur if the notification alert style is Alert rather than Banner
// updaterController.checkForUpdates(nil)
// }
//
// completionHandler()
// }
}
38 changes: 38 additions & 0 deletions Waker/Waker/Assets.xcassets/AccentColor.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file modified Waker/Waker/Assets.xcassets/AppIcon.appiconset/1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Waker/Waker/Assets.xcassets/AppIcon.appiconset/128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Waker/Waker/Assets.xcassets/AppIcon.appiconset/16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Waker/Waker/Assets.xcassets/AppIcon.appiconset/256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Waker/Waker/Assets.xcassets/AppIcon.appiconset/32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Waker/Waker/Assets.xcassets/AppIcon.appiconset/512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Waker/Waker/Assets.xcassets/AppIcon.appiconset/64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions Waker/Waker/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import SwiftUI
import Sparkle

class CheckForUpdatesViewModel: ObservableObject {
// This view model class publishes when new updates can be checked by the user
final class CheckForUpdatesViewModel: ObservableObject {
@Published var canCheckForUpdates = false

init(updater: SPUUpdater) {
updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)
Expand All @@ -34,7 +35,7 @@ struct CheckForUpdatesView: View {

var body: some View {
Button("Check for Updates", action: updater.checkForUpdates)
.buttonStyle(PlainButtonStyle())
.buttonStyle(.link)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)
}
}
16 changes: 16 additions & 0 deletions Waker/Waker/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSApplicationActivationPolicy</key>
<string>NSApplicationActivationPolicyAccessory</string>
<key>SUEnableDownloaderService</key>
<true/>
<key>SUEnableInstallerLauncherService</key>
<true/>
<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/ChenglongMa/waker-mac/main/Waker/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>haVE7DJh3aA5vlw6ycN+Ss0MP+CrGFEcbxLV1+Ovkaw=</string>
</dict>
</plist>
Loading

0 comments on commit bf53c3b

Please sign in to comment.