diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 1b9ffaf9..ab3928ab 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; - 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */; }; 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; }; 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.m */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; @@ -39,6 +38,7 @@ 6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; }; D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; }; D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F769278C9E29004A2160 /* CandidateUI */; }; + D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; }; D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; }; D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; }; D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; }; @@ -71,8 +71,6 @@ 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputMethodController.h; sourceTree = ""; }; 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InputMethodController.mm; sourceTree = ""; }; 6A0D4EC815FC0D6400ABF4B3 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -160,6 +158,7 @@ D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = ""; }; D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = ""; }; D427F768278C9D0D004A2160 /* CandidateUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CandidateUI; path = Packages/CandidateUI; sourceTree = ""; }; + D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; @@ -227,11 +226,10 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */, 6ACA41E715FC1D9000935EF6 /* Installer */, 6A0D4F4715FC0EB900ABF4B3 /* Resources */, - 6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */, - 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, + D427F76B278CA1BA004A2160 /* AppDelegate.swift */, D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */, D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */, D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */, @@ -543,7 +541,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */, + D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, D47F7DD5278C25A0002F9DD7 /* InputSourceHelper.swift in Sources */, D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */, diff --git a/Source/AppDelegate.h b/Source/AppDelegate.h deleted file mode 100644 index 96ee6e0c..00000000 --- a/Source/AppDelegate.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// AppDelegate.h -// -// Copyright (c) 2011 The McBopomofo Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - -@class PreferencesWindowController; - -@interface AppDelegate : NSObject -{ -@private - NSURLConnection *_updateCheckConnection; - BOOL _currentUpdateCheckIsForced; - NSMutableData *_receivingData; - NSURL *_updateNextStepURL; - PreferencesWindowController *_preferencesWindowController; -} - -- (void)checkForUpdate; -- (void)checkForUpdateForced:(BOOL)forced; -- (void)showPreferences; - -@property (weak, nonatomic) IBOutlet NSWindow *window; -@end diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m deleted file mode 100644 index d8b78a9b..00000000 --- a/Source/AppDelegate.m +++ /dev/null @@ -1,272 +0,0 @@ -// -// AppDelegate.m -// -// Copyright (c) 2011 The McBopomofo Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "AppDelegate.h" -#import "McBopomofo-Swift.h" - -extern void LTLoadLanguageModel(void); -extern void LTLoadUserLanguageModelFile(void); - -static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically"; -static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate"; -static NSString *kUpdateInfoEndpointKey = @"UpdateInfoEndpoint"; -static NSString *kUpdateInfoSiteKey = @"UpdateInfoSite"; -static const NSTimeInterval kNextCheckInterval = 86400.0; -static const NSTimeInterval kTimeoutInterval = 60.0; - -@interface AppDelegate () -@end - -@implementation AppDelegate -@synthesize window = _window; - -- (void)dealloc -{ - _preferencesWindowController = nil; - _updateCheckConnection = nil; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)inNotification -{ - LTLoadLanguageModel(); - LTLoadUserLanguageModelFile(); - - if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } - - [self checkForUpdate]; -} - -- (void)checkForUpdate -{ - [self checkForUpdateForced:NO]; -} - -- (void)checkForUpdateForced:(BOOL)forced -{ - if (_updateCheckConnection) { - // busy - return; - } - - _currentUpdateCheckIsForced = forced; - - // time for update? - if (!forced) { - if (![[NSUserDefaults standardUserDefaults] boolForKey:kCheckUpdateAutomatically]) { - return; - } - - NSDate *now = [NSDate date]; - NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:kNextUpdateCheckDateKey]; - if (![date isKindOfClass:[NSDate class]]) { - date = now; - } - - if ([now compare:date] == NSOrderedAscending) { - return; - } - } - - NSDate *nextUpdateDate = [NSDate dateWithTimeInterval:kNextCheckInterval sinceDate:[NSDate date]]; - [[NSUserDefaults standardUserDefaults] setObject:nextUpdateDate forKey:kNextUpdateCheckDateKey]; - - NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; - NSString *updateInfoURLString = [infoDict objectForKey:kUpdateInfoEndpointKey]; - if (![updateInfoURLString length]) { - return; - } - - NSURL *updateInfoURL = [NSURL URLWithString:updateInfoURLString]; - if (!updateInfoURL) { - return; - } - - NSURLRequest *request = [NSURLRequest requestWithURL:updateInfoURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:kTimeoutInterval]; - if (!request) { - return; - } -#if DEBUG - NSLog(@"about to request update url %@ ",updateInfoURL); -#endif - - if (_receivingData) { - _receivingData = nil; - } - - // create a new data buffer and connection - _receivingData = [[NSMutableData alloc] init]; - _updateCheckConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; - [_updateCheckConnection start]; -} - -- (void)showPreferences -{ - if (!_preferencesWindowController) { - _preferencesWindowController = [[PreferencesWindowController alloc] initWithWindowNibName:@"preferences"]; - } - [[_preferencesWindowController window] center]; - [[_preferencesWindowController window] orderFront:self]; -} - -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error -{ - BOOL isForcedCheck = _currentUpdateCheckIsForced; - - _receivingData = nil; - _updateCheckConnection = nil; - _currentUpdateCheckIsForced = NO; - - if (isForcedCheck) { - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Update Check Failed", nil) content:[NSString stringWithFormat:NSLocalizedString(@"There may be no internet connection or the server failed to respond.\n\nError message: %@", nil), [error localizedDescription]] confirmButtonTitle:NSLocalizedString(@"Dismiss", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; - } -} - -- (void)showNoUpdateAvailableAlert -{ - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of McBopomofo.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection -{ - id plist = [NSPropertyListSerialization propertyListWithData:_receivingData options:NSPropertyListImmutable format:NULL error:NULL]; -#if DEBUG - NSLog(@"plist %@",plist); -#endif - - BOOL isForcedCheck = _currentUpdateCheckIsForced; - - _receivingData = nil; - _updateCheckConnection = nil; - _currentUpdateCheckIsForced = NO; - - if (!plist) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - NSString *remoteVersion = [plist objectForKey:(id)kCFBundleVersionKey]; -#if DEBUG - NSLog(@"the remoteversion is %@",remoteVersion); -#endif - if (!remoteVersion) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - // TODO: Validate info (e.g. bundle identifier) - // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this - - NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; - NSString *currentVersion = [infoDict objectForKey:(id)kCFBundleVersionKey]; - NSComparisonResult result = [currentVersion compare:remoteVersion options:NSNumericSearch]; - - if (result != NSOrderedAscending) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - - NSString *siteInfoURLString = [plist objectForKey:kUpdateInfoSiteKey]; - if (!siteInfoURLString) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - NSURL *siteInfoURL = [NSURL URLWithString:siteInfoURLString]; - if (!siteInfoURL) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - _updateNextStepURL = siteInfoURL; - - NSDictionary *versionDescriptions = [plist objectForKey:@"Description"]; - NSString *versionDescription = @""; - if ([versionDescriptions isKindOfClass:[NSDictionary class]]) { - NSString *locale = @"en"; - NSArray *supportedLocales = [NSArray arrayWithObjects:@"en", @"zh-Hant", @"zh-Hans", nil]; - NSArray *preferredTags = [NSBundle preferredLocalizationsFromArray:supportedLocales]; - if ([preferredTags count]) { - locale = [preferredTags objectAtIndex:0]; - } - versionDescription = [versionDescriptions objectForKey:locale]; - if (!versionDescription) { - versionDescription = [versionDescriptions objectForKey:@"en"]; - } - - if (!versionDescription) { - versionDescription = @""; - } - else { - versionDescription = [@"\n\n" stringByAppendingString:versionDescription]; - } - } - - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@", nil), [infoDict objectForKey:@"CFBundleShortVersionString"], currentVersion, [plist objectForKey:@"CFBundleShortVersionString"], remoteVersion, versionDescription]; - - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"New Version Available", nil) content:content confirmButtonTitle:NSLocalizedString(@"Visit Website", nil) cancelButtonTitle:NSLocalizedString(@"Not Now", nil) cancelAsDefault:NO delegate:self]; -} - -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data -{ - [_receivingData appendData:data]; -} - -- (void)nonModalAlertWindowControllerDidConfirm:(NonModalAlertWindowController *)controller -{ - if (_updateNextStepURL) { - [[NSWorkspace sharedWorkspace] openURL:_updateNextStepURL]; - } - - _updateNextStepURL = nil; -} - -- (void)nonModalAlertWindowControllerDidCancel:(NonModalAlertWindowController *)controller -{ - _updateNextStepURL = nil; -} - -@end diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift new file mode 100644 index 00000000..c2fd7a3a --- /dev/null +++ b/Source/AppDelegate.swift @@ -0,0 +1,216 @@ +// +// AppDelegate.swift +// +// Copyright (c) 2011 The McBopomofo Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) +// Weizhong Yang (@zonble) +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa +import InputMethodKit + +private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" +private let kNextUpdateCheckDateKey = "NextUpdateCheckDate" +private let kUpdateInfoEndpointKey = "UpdateInfoEndpoint" +private let kUpdateInfoSiteKey = "UpdateInfoSite" +private let kNextCheckInterval: TimeInterval = 86400.0 +private let kTimeoutInterval: TimeInterval = 60.0 + +@objc(AppDelegate) +class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate { + + @IBOutlet weak var window: NSWindow? + private var preferencesWindowController: PreferencesWindowController? + private var checkTask: URLSessionTask? + private var updateNextStepURL: URL? + + func applicationDidFinishLaunching(_ notification: Notification) { + LTLoadLanguageModel() + LTLoadUserLanguageModelFile() + + if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { + UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically) + UserDefaults.standard.synchronize() + } + checkForUpdate() + } + + @objc func showPreferences() { + if preferencesWindowController == nil { + preferencesWindowController = PreferencesWindowController(windowNibName: "preferences") + } + preferencesWindowController?.window?.center() + preferencesWindowController?.window?.orderFront(self) + } + + @objc(checkForUpdate) + func checkForUpdate() { + checkForUpdate(forced: false) + } + + @objc(checkForUpdateForced:) + func checkForUpdate(forced: Bool) { + + if checkTask != nil { + // busy + return + } + + // time for update? + if !forced { + if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false { + return + } + let now = Date() + let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now + if now.compare(date) == .orderedAscending { + return + } + } + + let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date()) + UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey) + + guard let infoDict = Bundle.main.infoDictionary, + let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, + let updateInfoURL = URL(string: updateInfoURLString) else { + return + } + + let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) + + func showNoUpdateAvailableAlert() { + + } + + let task = URLSession.shared.dataTask(with: request) { data, response, error in + defer { + self.checkTask = nil + } + + if let error = error { + if forced { + let title = NSLocalizedString("Update Check Failed", comment: "") + let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), error.localizedDescription) + let buttonTitle = NSLocalizedString("Dismiss", comment: "") + + DispatchQueue.main.async { + NonModalAlertWindowController.shared.show(title:title , content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) + } + } + return + } + + do { + guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], + let remoteVersion = plist[kCFBundleVersionKey] as? String, + let infoDict = Bundle.main.infoDictionary + else { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + return + } + + // TODO: Validate info (e.g. bundle identifier) + // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this + + let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" + let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil) + + if result != .orderedAscending { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + } + + guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, + let siteInfoURL = URL(string: siteInfoURLString) else { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + return + } + + self.updateNextStepURL = siteInfoURL + + var versionDescription = "" + let versionDescriptions = plist["Description"] as? [AnyHashable: Any] + if let versionDescriptions = versionDescriptions { + var locale = "en" + let supportedLocales = ["en", "zh-Hant", "zh-Hans"] + let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) + if let first = preferredTags.first { + locale = first + } + versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? "" + if !versionDescription.isEmpty { + versionDescription = "\n\n" + versionDescription + } + } + + let content = String(format: NSLocalizedString("You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@", comment: ""), + infoDict["CFBundleShortVersionString"] as? String ?? "", + currentVersion, + plist["CFBundleShortVersionString"] as? String ?? "", + remoteVersion, + versionDescription) + DispatchQueue.main.async { + NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: "") , content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self) + } + + } catch { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + } + } + checkTask = task + task.resume() + } + + func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController) { + if let updateNextStepURL = updateNextStepURL { + NSWorkspace.shared.open(updateNextStepURL) + } + updateNextStepURL = nil + } + + func nonModalAlertWindowControllerDidCancel(_ controller: NonModalAlertWindowController) { + updateNextStepURL = nil + } +} diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 9e3b7b5a..0b075cf9 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -38,7 +38,6 @@ #import #import "OVStringHelper.h" #import "OVUTF8Helper.h" -#import "AppDelegate.h" #import "McBopomofo-Swift.h" @import CandidateUI; diff --git a/Source/McBopomofo-Bridging-Header.h b/Source/McBopomofo-Bridging-Header.h index 1b2cb5d6..032f1391 100644 --- a/Source/McBopomofo-Bridging-Header.h +++ b/Source/McBopomofo-Bridging-Header.h @@ -2,3 +2,7 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +extern void LTLoadLanguageModel(void); +extern void LTLoadUserLanguageModelFile(void); + +