AppDelegate // Make UpdateAPI into a standalone module.
This commit is contained in:
parent
1d40945706
commit
18fd4975ff
|
@ -27,156 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import InputMethodKit
|
import InputMethodKit
|
||||||
|
|
||||||
private let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
|
||||||
private let kNextUpdateCheckDateKey = "NextUpdateCheckDate"
|
|
||||||
private let kUpdateInfoEndpointKey = "UpdateInfoEndpoint"
|
|
||||||
private let kUpdateInfoSiteKey = "UpdateInfoSite"
|
|
||||||
private let kVersionDescription = "VersionDescription"
|
|
||||||
private let kNextCheckInterval: TimeInterval = 86400.0
|
|
||||||
private let kTimeoutInterval: TimeInterval = 60.0
|
|
||||||
|
|
||||||
struct VersionUpdateReport {
|
|
||||||
var siteUrl: URL?
|
|
||||||
var currentShortVersion: String = ""
|
|
||||||
var currentVersion: String = ""
|
|
||||||
var remoteShortVersion: String = ""
|
|
||||||
var remoteVersion: String = ""
|
|
||||||
var versionDescription: String = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VersionUpdateApiResult {
|
|
||||||
case shouldUpdate(report: VersionUpdateReport)
|
|
||||||
case noNeedToUpdate
|
|
||||||
case ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VersionUpdateApiError: Error, LocalizedError {
|
|
||||||
case connectionError(message: String)
|
|
||||||
|
|
||||||
var errorDescription: String? {
|
|
||||||
switch self {
|
|
||||||
case .connectionError(let message):
|
|
||||||
return String(
|
|
||||||
format: NSLocalizedString(
|
|
||||||
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
|
||||||
comment: ""), message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VersionUpdateApi {
|
|
||||||
static func check(
|
|
||||||
forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> Void
|
|
||||||
) -> URLSessionTask? {
|
|
||||||
guard let infoDict = Bundle.main.infoDictionary,
|
|
||||||
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
|
|
||||||
let updateInfoURL = URL(string: updateInfoURLString)
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = URLRequest(
|
|
||||||
url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData,
|
|
||||||
timeoutInterval: kTimeoutInterval)
|
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
if let error = error {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
forced
|
|
||||||
? callback(
|
|
||||||
.failure(
|
|
||||||
VersionUpdateApiError.connectionError(
|
|
||||||
message: error.localizedDescription)))
|
|
||||||
: callback(.success(.ignored))
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
forced
|
|
||||||
? callback(.success(.noNeedToUpdate))
|
|
||||||
: callback(.success(.ignored))
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
forced
|
|
||||||
? callback(.success(.noNeedToUpdate))
|
|
||||||
: callback(.success(.ignored))
|
|
||||||
}
|
|
||||||
IME.prtDebugIntel(
|
|
||||||
"vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
IME.prtDebugIntel(
|
|
||||||
"vChewingDebug: Update // New version detected, proceeding to the next phase.")
|
|
||||||
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
|
|
||||||
let siteInfoURL = URL(string: siteInfoURLString)
|
|
||||||
else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
forced
|
|
||||||
? callback(.success(.noNeedToUpdate))
|
|
||||||
: callback(.success(.ignored))
|
|
||||||
}
|
|
||||||
IME.prtDebugIntel(
|
|
||||||
"vChewingDebug: Update // Failed from retrieving / parsing URL intel.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
IME.prtDebugIntel(
|
|
||||||
"vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.")
|
|
||||||
var report = VersionUpdateReport(siteUrl: siteInfoURL)
|
|
||||||
var versionDescription = ""
|
|
||||||
let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any]
|
|
||||||
if let versionDescriptions = versionDescriptions {
|
|
||||||
var locale = "en"
|
|
||||||
let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"]
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? ""
|
|
||||||
report.currentVersion = currentVersion
|
|
||||||
report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? ""
|
|
||||||
report.remoteVersion = remoteVersion
|
|
||||||
report.versionDescription = versionDescription
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
callback(.success(.shouldUpdate(report: report)))
|
|
||||||
}
|
|
||||||
IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.")
|
|
||||||
} catch {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task.resume()
|
|
||||||
return task
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(AppDelegate)
|
@objc(AppDelegate)
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
||||||
FSEventStreamHelperDelegate
|
FSEventStreamHelperDelegate
|
||||||
|
@ -220,7 +70,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
mgrPrefs.setMissingDefaults()
|
mgrPrefs.setMissingDefaults()
|
||||||
|
|
||||||
// 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。
|
// 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。
|
||||||
if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true {
|
if (UserDefaults.standard.object(forKey: VersionUpdateApi.kCheckUpdateAutomatically) != nil) == true {
|
||||||
checkForUpdate()
|
checkForUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,18 +112,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
|
|
||||||
// time for update?
|
// time for update?
|
||||||
if !forced {
|
if !forced {
|
||||||
if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false {
|
if UserDefaults.standard.bool(forKey: VersionUpdateApi.kCheckUpdateAutomatically) == false {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let now = Date()
|
let now = Date()
|
||||||
let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now
|
let date = UserDefaults.standard.object(forKey: VersionUpdateApi.kNextUpdateCheckDateKey) as? Date ?? now
|
||||||
if now.compare(date) == .orderedAscending {
|
if now.compare(date) == .orderedAscending {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date())
|
let nextUpdateDate = Date(timeInterval: VersionUpdateApi.kNextCheckInterval, since: Date())
|
||||||
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
|
UserDefaults.standard.set(nextUpdateDate, forKey: VersionUpdateApi.kNextUpdateCheckDateKey)
|
||||||
|
|
||||||
checkTask = VersionUpdateApi.check(forced: forced) { [self] result in
|
checkTask = VersionUpdateApi.check(forced: forced) { [self] result in
|
||||||
defer {
|
defer {
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||||
|
// All possible vChewing-specific modifications are of:
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
/*
|
||||||
|
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:
|
||||||
|
|
||||||
|
1. The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
2. No trademark license is granted to use the trade names, trademarks, service
|
||||||
|
marks, or product names of Contributor, except as required to fulfill notice
|
||||||
|
requirements above.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
struct VersionUpdateReport {
|
||||||
|
var siteUrl: URL?
|
||||||
|
var currentShortVersion: String = ""
|
||||||
|
var currentVersion: String = ""
|
||||||
|
var remoteShortVersion: String = ""
|
||||||
|
var remoteVersion: String = ""
|
||||||
|
var versionDescription: String = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VersionUpdateApiResult {
|
||||||
|
case shouldUpdate(report: VersionUpdateReport)
|
||||||
|
case noNeedToUpdate
|
||||||
|
case ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VersionUpdateApiError: Error, LocalizedError {
|
||||||
|
case connectionError(message: String)
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .connectionError(let message):
|
||||||
|
return String(
|
||||||
|
format: NSLocalizedString(
|
||||||
|
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
||||||
|
comment: ""), message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VersionUpdateApi {
|
||||||
|
static let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
||||||
|
static let kNextUpdateCheckDateKey = "NextUpdateCheckDate"
|
||||||
|
static let kUpdateInfoEndpointKey = "UpdateInfoEndpoint"
|
||||||
|
static let kUpdateInfoSiteKey = "UpdateInfoSite"
|
||||||
|
static let kVersionDescription = "VersionDescription"
|
||||||
|
static let kNextCheckInterval: TimeInterval = 86400.0
|
||||||
|
static let kTimeoutInterval: TimeInterval = 60.0
|
||||||
|
static func check(
|
||||||
|
forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> Void
|
||||||
|
) -> URLSessionTask? {
|
||||||
|
guard let infoDict = Bundle.main.infoDictionary,
|
||||||
|
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
|
||||||
|
let updateInfoURL = URL(string: updateInfoURLString)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = URLRequest(
|
||||||
|
url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData,
|
||||||
|
timeoutInterval: kTimeoutInterval)
|
||||||
|
let task = URLSession.shared.dataTask(with: request) { data, _, error in
|
||||||
|
if let error = error {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
forced
|
||||||
|
? callback(
|
||||||
|
.failure(
|
||||||
|
VersionUpdateApiError.connectionError(
|
||||||
|
message: error.localizedDescription)))
|
||||||
|
: callback(.success(.ignored))
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
forced
|
||||||
|
? callback(.success(.noNeedToUpdate))
|
||||||
|
: callback(.success(.ignored))
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
forced
|
||||||
|
? callback(.success(.noNeedToUpdate))
|
||||||
|
: callback(.success(.ignored))
|
||||||
|
}
|
||||||
|
IME.prtDebugIntel(
|
||||||
|
"vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
IME.prtDebugIntel(
|
||||||
|
"vChewingDebug: Update // New version detected, proceeding to the next phase.")
|
||||||
|
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
|
||||||
|
let siteInfoURL = URL(string: siteInfoURLString)
|
||||||
|
else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
forced
|
||||||
|
? callback(.success(.noNeedToUpdate))
|
||||||
|
: callback(.success(.ignored))
|
||||||
|
}
|
||||||
|
IME.prtDebugIntel(
|
||||||
|
"vChewingDebug: Update // Failed from retrieving / parsing URL intel.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
IME.prtDebugIntel(
|
||||||
|
"vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.")
|
||||||
|
var report = VersionUpdateReport(siteUrl: siteInfoURL)
|
||||||
|
var versionDescription = ""
|
||||||
|
let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any]
|
||||||
|
if let versionDescriptions = versionDescriptions {
|
||||||
|
var locale = "en"
|
||||||
|
let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? ""
|
||||||
|
report.currentVersion = currentVersion
|
||||||
|
report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? ""
|
||||||
|
report.remoteVersion = remoteVersion
|
||||||
|
report.versionDescription = versionDescription
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
callback(.success(.shouldUpdate(report: report)))
|
||||||
|
}
|
||||||
|
IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.")
|
||||||
|
} catch {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,7 @@
|
||||||
5BD05C6827B2BBEF004C4F1D /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6327B2BBEF004C4F1D /* Content.swift */; };
|
5BD05C6827B2BBEF004C4F1D /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6327B2BBEF004C4F1D /* Content.swift */; };
|
||||||
5BD05C6927B2BBEF004C4F1D /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */; };
|
5BD05C6927B2BBEF004C4F1D /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */; };
|
||||||
5BD05C6A27B2BBEF004C4F1D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */; };
|
5BD05C6A27B2BBEF004C4F1D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */; };
|
||||||
|
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */; };
|
||||||
5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; };
|
5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; };
|
||||||
5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; };
|
5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; };
|
||||||
5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; };
|
5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; };
|
||||||
|
@ -212,6 +213,7 @@
|
||||||
5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = "<group>"; };
|
5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = "<group>"; };
|
||||||
5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
|
||||||
5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||||
|
5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = apiUpdate.swift; sourceTree = "<group>"; };
|
||||||
5BDCBB4227B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
5BDCBB4227B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = "<group>"; };
|
||||||
5BDCBB4327B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = "<group>"; };
|
5BDCBB4327B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = "<group>"; };
|
||||||
5BDCBB4527B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmPrefWindow.strings"; sourceTree = "<group>"; };
|
5BDCBB4527B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmPrefWindow.strings"; sourceTree = "<group>"; };
|
||||||
|
@ -410,6 +412,7 @@
|
||||||
5B62A32227AE756300A19448 /* IMEModules */ = {
|
5B62A32227AE756300A19448 /* IMEModules */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */,
|
||||||
D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */,
|
D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */,
|
||||||
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
|
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
|
||||||
5B5E535127EF261400C6AA1E /* IME.swift */,
|
5B5E535127EF261400C6AA1E /* IME.swift */,
|
||||||
|
@ -997,6 +1000,7 @@
|
||||||
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */,
|
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */,
|
||||||
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
|
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
|
||||||
D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */,
|
D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */,
|
||||||
|
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue