vChewing-macOS/Installer/MainView.swift

162 lines
6.4 KiB
Swift

// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// 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 defined in MIT License.
import AppKit
import SwiftUI
public struct MainView: View {
static let strCopyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String ?? "BAD_COPYRIGHT_LABEL"
@State var pendingSheetPresenting = false
@State var isShowingAlertForFailedInstallation = false
@State var isShowingAlertForMissingPostInstall = false
@State var isShowingPostInstallNotification = false
@State var currentAlertContent: AlertType = .nothing
@State var isCancelButtonEnabled = true
@State var isAgreeButtonEnabled = true
@State var isPreviousVersionNotFullyDeactivated = false
@State var isTranslocationFinished: Bool?
@State var isUpgrading: Bool = false
var translocationRemovalStartTime: Date?
@State var timeRemaining = 60
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
public init() {
if FileManager.default.fileExists(atPath: kTargetPartialPath) {
let currentBundle = Bundle(path: kTargetPartialPath)
let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
if shortVersion != nil, let currentVersion = currentVersion,
currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending
{
isUpgrading = true
}
}
}
public var body: some View {
GroupBox {
VStack(alignment: .leading, spacing: 6) {
VStack(alignment: .leading) {
HStack(alignment: .center) {
if let icon = NSImage(named: "IconSansMargin") {
Image(nsImage: icon).resizable().frame(width: 90, height: 90)
}
VStack(alignment: .leading) {
HStack {
Text("i18n:installer.APP_NAME").fontWeight(.heavy).lineLimit(1)
Text("v\(versionString) Build \(installingVersion)").lineLimit(1)
}.fixedSize()
Text("i18n:installer.APP_DERIVED_FROM").font(.custom("Tahoma", size: 11))
Text(Self.strCopyrightLabel).font(.custom("Tahoma", size: 11))
Text("i18n:installer.DEV_CREW").font(.custom("Tahoma", size: 11)).padding([.vertical], 2)
}
}
GroupBox(label: Text("i18n:installer.LICENSE_TITLE")) {
ScrollView(.vertical, showsIndicators: true) {
HStack {
Text(eulaContent + "\n" + eulaContentUpstream).textSelection(.enabled)
.frame(maxWidth: 455)
.font(.custom("Tahoma", size: 11))
Spacer()
}
}.padding(4).frame(height: 128)
}
Text("i18n:installer.EULA_PROMPT_NOTICE").bold().padding(.bottom, 2)
}
Divider()
HStack(alignment: .top) {
Text("i18n:installer.DISCLAIMER_TEXT")
.font(.custom("Tahoma", size: 11))
.opacity(0.5)
.frame(maxWidth: .infinity)
VStack(spacing: 4) {
Button { installationButtonClicked() } label: {
Text(isUpgrading ? "i18n:installer.DO_APP_UPGRADE" : "i18n:installer.ACCEPT_INSTALLATION")
.bold().frame(width: 114)
}
.keyboardShortcut(.defaultAction)
.disabled(!isCancelButtonEnabled)
Button(role: .cancel) { NSApp.terminateWithDelay() } label: {
Text("i18n:installer.CANCEL_INSTALLATION").frame(width: 114)
}
.keyboardShortcut(.cancelAction)
.disabled(!isAgreeButtonEnabled)
}.fixedSize(horizontal: true, vertical: true)
}
Spacer()
}
.font(.custom("Tahoma", size: 12))
.padding(4)
}
// ALERTS
.alert(AlertType.installationFailed.title, isPresented: $isShowingAlertForFailedInstallation) {
Button(role: .cancel) { NSApp.terminateWithDelay() } label: { Text("Cancel") }
} message: {
Text(AlertType.installationFailed.message)
}
.alert(AlertType.missingAfterRegistration.title, isPresented: $isShowingAlertForMissingPostInstall) {
Button(role: .cancel) { NSApp.terminateWithDelay() } label: { Text("Abort") }
} message: {
Text(AlertType.missingAfterRegistration.message)
}
.alert(currentAlertContent.title, isPresented: $isShowingPostInstallNotification) {
Button(role: .cancel) { NSApp.terminateWithDelay() } label: {
Text(currentAlertContent == .postInstallWarning ? "Continue" : "OK")
}
} message: {
Text(currentAlertContent.message)
}
// SHEET FOR STOPPING THE OLD VERSION
.sheet(isPresented: $pendingSheetPresenting) {
// TODO: Tasks after sheet gets closed by `dismiss()`.
} content: {
Text("i18n:installer.STOPPING_THE_OLD_VERSION").frame(width: 407, height: 144)
.onReceive(timer) { _ in
if timeRemaining > 0 {
if Reloc.isAppBundleTranslocated(atPath: kTargetPartialPath) == false {
pendingSheetPresenting = false
isTranslocationFinished = true
installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: false
)
}
timeRemaining -= 1
} else {
pendingSheetPresenting = false
isTranslocationFinished = false
installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: true
)
}
}
}
// OTHER
.padding(12)
.frame(width: 533, alignment: .topLeading)
.navigationTitle(mainWindowTitle)
.fixedSize()
.foregroundStyle(Color(nsColor: NSColor.textColor))
.background(Color(nsColor: NSColor.windowBackgroundColor))
.clipShape(RoundedRectangle(cornerRadius: 16))
.frame(minWidth: 533, idealWidth: 533, maxWidth: 533,
minHeight: 386, idealHeight: 386, maxHeight: 386,
alignment: .top)
}
func installationButtonClicked() {
isCancelButtonEnabled = false
isAgreeButtonEnabled = false
removeThenInstallInputMethod()
}
}