mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
New stuff
This commit is contained in:
parent
e59a9fe38f
commit
5fef72528d
6 changed files with 256 additions and 104 deletions
|
|
@ -719,18 +719,18 @@
|
|||
"$(PROJECT_DIR)/StikJIT/idevice",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 2.3.5;
|
||||
MARKETING_VERSION = 26.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "StikJIT/StikJIT-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
XROS_DEPLOYMENT_TARGET = 2.2;
|
||||
};
|
||||
name = Debug;
|
||||
|
|
@ -775,17 +775,17 @@
|
|||
"$(PROJECT_DIR)/StikJIT/idevice",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 2.3.5;
|
||||
MARKETING_VERSION = 26.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stik.sj;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "StikJIT/StikJIT-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
XROS_DEPLOYMENT_TARGET = 2.2;
|
||||
};
|
||||
name = Release;
|
||||
|
|
|
|||
|
|
@ -511,20 +511,6 @@ struct HeartbeatApp: App {
|
|||
@StateObject private var themeExpansionManager = ThemeExpansionManager()
|
||||
@Environment(\.scenePhase) private var scenePhase // Observe scene lifecycle
|
||||
|
||||
let urls: [String] = [
|
||||
"https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist",
|
||||
"https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg",
|
||||
"https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg.trustcache"
|
||||
]
|
||||
|
||||
let outputDir: String = "DDI"
|
||||
|
||||
let outputFiles: [String] = [
|
||||
"DDI/BuildManifest.plist",
|
||||
"DDI/Image.dmg",
|
||||
"DDI/Image.dmg.trustcache"
|
||||
]
|
||||
|
||||
init() {
|
||||
registerAdvancedOptionsDefault()
|
||||
ContinuedProcessingManager.shared.configureIfNeeded()
|
||||
|
|
@ -583,16 +569,20 @@ struct HeartbeatApp: App {
|
|||
BackgroundContainer {
|
||||
MainTabView()
|
||||
.onAppear {
|
||||
let fileManager = FileManager.default
|
||||
for (index, urlString) in urls.enumerated() {
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(outputFiles[index])
|
||||
if !fileManager.fileExists(atPath: destinationURL.path) {
|
||||
downloadFile(from: urlString, to: destinationURL) { result in
|
||||
if (result != "") {
|
||||
Task {
|
||||
let fileManager = FileManager.default
|
||||
for item in ddiDownloadItems {
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
||||
if fileManager.fileExists(atPath: destinationURL.path) { continue }
|
||||
do {
|
||||
try await downloadFile(from: item.urlString, to: destinationURL)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert_title = "An Error has Occurred"
|
||||
alert_string = "[Download DDI Error]: " + result
|
||||
alert_string = "[Download DDI Error]: \(error.localizedDescription)"
|
||||
show_alert = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -961,75 +951,76 @@ public func showAlert(title: String, message: String, showOk: Bool, showTryAgain
|
|||
}
|
||||
}
|
||||
|
||||
func downloadFile(from urlString: String, to destinationURL: URL, completion: @escaping (String) -> Void) {
|
||||
let fileManager = FileManager.default
|
||||
let documentsDirectory = URL.documentsDirectory
|
||||
private struct DDIDownloadItem {
|
||||
let name: String
|
||||
let relativePath: String
|
||||
let urlString: String
|
||||
}
|
||||
|
||||
private let ddiDownloadItems: [DDIDownloadItem] = [
|
||||
.init(
|
||||
name: "Build Manifest",
|
||||
relativePath: "DDI/BuildManifest.plist",
|
||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist"
|
||||
),
|
||||
.init(
|
||||
name: "Image",
|
||||
relativePath: "DDI/Image.dmg",
|
||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg"
|
||||
),
|
||||
.init(
|
||||
name: "TrustCache",
|
||||
relativePath: "DDI/Image.dmg.trustcache",
|
||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg.trustcache"
|
||||
)
|
||||
]
|
||||
|
||||
enum DDIDownloadError: LocalizedError {
|
||||
case invalidURL(String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL(let string):
|
||||
return "Invalid download URL: \(string)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadFile(from urlString: String, to destinationURL: URL) async throws {
|
||||
guard let url = URL(string: urlString) else {
|
||||
print("Invalid URL: \(urlString)")
|
||||
completion("[Internal Invalid URL error]")
|
||||
return
|
||||
throw DDIDownloadError.invalidURL(urlString)
|
||||
}
|
||||
|
||||
let task = URLSession.shared.downloadTask(with: url) { (tempLocalUrl, response, error) in
|
||||
guard let tempLocalUrl = tempLocalUrl, error == nil else {
|
||||
print("Error downloading file from \(urlString): \(String(describing: error))")
|
||||
completion("Are you connected to the internet? [Download Failed]")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil)
|
||||
try fileManager.moveItem(at: tempLocalUrl, to: destinationURL)
|
||||
print("Downloaded \(urlString) to \(destinationURL.path)")
|
||||
} catch {
|
||||
print("Error saving file: \(error)")
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
completion("")
|
||||
}
|
||||
|
||||
func redownloadDDI() {
|
||||
let (tempLocalUrl, _) = try await URLSession.shared.download(from: url)
|
||||
let fileManager = FileManager.default
|
||||
let ddiFiles = [
|
||||
"DDI/BuildManifest.plist",
|
||||
"DDI/Image.dmg",
|
||||
"DDI/Image.dmg.trustcache"
|
||||
]
|
||||
|
||||
for file in ddiFiles {
|
||||
let fileURL = URL.documentsDirectory.appendingPathComponent(file)
|
||||
if fileManager.fileExists(atPath: fileURL.path) {
|
||||
do {
|
||||
try fileManager.removeItem(at: fileURL)
|
||||
print("Deleted \(file)")
|
||||
} catch {
|
||||
print("Error deleting \(file): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let urls = [
|
||||
"https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist",
|
||||
"https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg",
|
||||
"https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg.trustcache"
|
||||
]
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for (index, urlString) in urls.enumerated() {
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(ddiFiles[index])
|
||||
group.enter()
|
||||
downloadFile(from: urlString, to: destinationURL) { result in
|
||||
if (result != "") {
|
||||
showAlert(title: "An Error has Occurred", message: "[Download DDI Error]: " + result, showOk: true)
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
showAlert(title: "Success", message: "DDI files have been redownloaded.", showOk: true)
|
||||
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(),
|
||||
withIntermediateDirectories: true)
|
||||
if fileManager.fileExists(atPath: destinationURL.path) {
|
||||
try fileManager.removeItem(at: destinationURL)
|
||||
}
|
||||
try fileManager.moveItem(at: tempLocalUrl, to: destinationURL)
|
||||
}
|
||||
|
||||
func redownloadDDI(progressHandler: ((Double, String) -> Void)? = nil) async throws {
|
||||
let fileManager = FileManager.default
|
||||
let totalStages = Double(ddiDownloadItems.count + 1)
|
||||
var completedStages = 0.0
|
||||
|
||||
progressHandler?(0.0, "Removing existing DDI files…")
|
||||
for item in ddiDownloadItems {
|
||||
let fileURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
||||
if fileManager.fileExists(atPath: fileURL.path) {
|
||||
try fileManager.removeItem(at: fileURL)
|
||||
}
|
||||
}
|
||||
completedStages += 1.0
|
||||
progressHandler?(completedStages / totalStages, "Starting downloads…")
|
||||
|
||||
for item in ddiDownloadItems {
|
||||
progressHandler?(completedStages / totalStages, "Downloading \(item.name)…")
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
||||
try await downloadFile(from: item.urlString, to: destinationURL)
|
||||
completedStages += 1.0
|
||||
progressHandler?(completedStages / totalStages, "\(item.name) ready")
|
||||
}
|
||||
progressHandler?(1.0, "DDI download complete.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ final class ContinuedProcessingManager {
|
|||
func configureIfNeeded() {
|
||||
handler.configureIfNeeded()
|
||||
}
|
||||
|
||||
func cancelPendingTasks() {
|
||||
handler.cancelPendingTasks()
|
||||
}
|
||||
|
||||
func begin(title: String, subtitle: String) {
|
||||
handler.begin(title: title, subtitle: subtitle)
|
||||
|
|
@ -42,6 +46,7 @@ final class ContinuedProcessingManager {
|
|||
private protocol ContinuedProcessingHandling: AnyObject {
|
||||
var isSupported: Bool { get }
|
||||
func configureIfNeeded()
|
||||
func cancelPendingTasks()
|
||||
func begin(title: String, subtitle: String)
|
||||
func updateProgress(_ fraction: Double)
|
||||
func finish(success: Bool)
|
||||
|
|
@ -50,6 +55,7 @@ private protocol ContinuedProcessingHandling: AnyObject {
|
|||
private final class NoopContinuedProcessingHandler: ContinuedProcessingHandling {
|
||||
var isSupported: Bool { false }
|
||||
func configureIfNeeded() {}
|
||||
func cancelPendingTasks() {}
|
||||
func begin(title: String, subtitle: String) {}
|
||||
func updateProgress(_ fraction: Double) {}
|
||||
func finish(success: Bool) {}
|
||||
|
|
@ -95,6 +101,8 @@ private final class ModernContinuedProcessingHandler: ContinuedProcessingHandlin
|
|||
}
|
||||
}
|
||||
guard reserved else { return }
|
||||
// Clear any stale request that might block new submissions.
|
||||
scheduler.cancel(taskRequestWithIdentifier: taskIdentifier)
|
||||
let request = BGContinuedProcessingTaskRequest(identifier: taskIdentifier,
|
||||
title: title,
|
||||
subtitle: subtitle)
|
||||
|
|
@ -110,6 +118,18 @@ private final class ModernContinuedProcessingHandler: ContinuedProcessingHandlin
|
|||
}
|
||||
}
|
||||
|
||||
func cancelPendingTasks() {
|
||||
queue.async { [weak self] in
|
||||
guard let self else { return }
|
||||
if let task = activeTask {
|
||||
task.setTaskCompleted(success: false)
|
||||
activeTask = nil
|
||||
}
|
||||
pendingMetadata = nil
|
||||
scheduler.cancel(taskRequestWithIdentifier: taskIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
func updateProgress(_ fraction: Double) {
|
||||
queue.async { [weak self] in
|
||||
guard let task = self?.activeTask else { return }
|
||||
|
|
|
|||
|
|
@ -91,6 +91,32 @@ struct HomeView: View {
|
|||
!ddiMounted &&
|
||||
!heartbeatOK
|
||||
}
|
||||
private var sanitizedUsername: String {
|
||||
let trimmed = username.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmed.isEmpty ? "there" : trimmed
|
||||
}
|
||||
private var greetingTitle: String {
|
||||
"\(timeOfDayGreeting), \(sanitizedUsername)!"
|
||||
}
|
||||
private var greetingSubtitle: String {
|
||||
if canConnectByApp {
|
||||
return "You're all set. Connect whenever you're ready."
|
||||
} else if !pairingFileExists {
|
||||
return "Import your pairing file to start debugging."
|
||||
} else if !ddiMounted {
|
||||
return "Mount the DDI to finish preparing your device."
|
||||
}
|
||||
return "Complete the steps below to get ready."
|
||||
}
|
||||
private var timeOfDayGreeting: String {
|
||||
let hour = Calendar.current.component(.hour, from: Date())
|
||||
switch hour {
|
||||
case 5..<12: return "Good morning"
|
||||
case 12..<17: return "Good afternoon"
|
||||
case 17..<22: return "Good evening"
|
||||
default: return "Hello"
|
||||
}
|
||||
}
|
||||
private var shouldPromptForWiFi: Bool {
|
||||
pairingFileLikelyInvalid && !wifiConnected && isCellularActive
|
||||
}
|
||||
|
|
@ -105,6 +131,7 @@ struct HomeView: View {
|
|||
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
welcomeCard
|
||||
setupCard
|
||||
connectCard
|
||||
// if pairingFileExists {
|
||||
|
|
@ -352,6 +379,20 @@ struct HomeView: View {
|
|||
|
||||
// MARK: - Styled Sections
|
||||
|
||||
private var welcomeCard: some View {
|
||||
homeCard {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(greetingTitle)
|
||||
.font(.system(.title2, design: .rounded).weight(.semibold))
|
||||
.foregroundStyle(.primary)
|
||||
Text(greetingSubtitle)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private var setupCard: some View {
|
||||
homeCard {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
|
|
|
|||
|
|
@ -150,10 +150,12 @@ struct ProfileView: View {
|
|||
}
|
||||
|
||||
|
||||
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
|
||||
@AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue
|
||||
@Environment(\.themeExpansionManager) private var themeExpansion
|
||||
private var backgroundStyle: BackgroundStyle { themeExpansion?.backgroundStyle(for: appThemeRaw) ?? AppTheme.system.backgroundStyle }
|
||||
private var preferredScheme: ColorScheme? { themeExpansion?.preferredColorScheme(for: appThemeRaw) }
|
||||
private var accentColor: Color { themeExpansion?.resolvedAccentColor(from: customAccentColorHex) ?? .blue }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
|
|
@ -290,13 +292,13 @@ struct ProfileView: View {
|
|||
.foregroundColor(.secondary)
|
||||
.textSelection(.enabled)
|
||||
Spacer()
|
||||
HStack(spacing: 12) {
|
||||
Button { saveProfile(profile: entry) } label: { Image(systemName: "square.and.arrow.down") }
|
||||
Button(role: .destructive) {
|
||||
removeTargetName = entry.appName
|
||||
removeTargetUUID = entry.uuid
|
||||
confirmRemove = true
|
||||
} label: { Image(systemName: "trash") }
|
||||
HStack(spacing: 10) {
|
||||
profileActionButton(icon: "square.and.arrow.down", color: accentColor) {
|
||||
saveProfile(profile: entry)
|
||||
}
|
||||
profileActionButton(icon: "trash", color: .refreshRed) {
|
||||
removeProfilePrompt(entry: entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
|
|
@ -331,6 +333,31 @@ struct ProfileView: View {
|
|||
.shadow(color: .black.opacity(0.15), radius: 12, x: 0, y: 4)
|
||||
}
|
||||
|
||||
private func profileActionButton(icon: String, color: Color, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 13, weight: .bold))
|
||||
.foregroundStyle(color.contrastText())
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 12)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(color.opacity(0.9))
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.stroke(Color.white.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private func removeProfilePrompt(entry: Profile) {
|
||||
removeTargetName = entry.appName
|
||||
removeTargetUUID = entry.uuid
|
||||
confirmRemove = true
|
||||
}
|
||||
|
||||
func loadProfiles() async {
|
||||
working = true
|
||||
do {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ struct SettingsView: View {
|
|||
@State private var showRemovePairingFileDialog = false
|
||||
@State private var is_lc = false
|
||||
@State private var showColorPickerPopup = false
|
||||
@State private var showDDIConfirmation = false
|
||||
@State private var isRedownloadingDDI = false
|
||||
@State private var ddiDownloadProgress: Double = 0.0
|
||||
@State private var ddiStatusMessage: String = ""
|
||||
@State private var ddiResultMessage: (text: String, isError: Bool)?
|
||||
|
||||
@State private var showingDisplayView = false
|
||||
|
||||
|
|
@ -201,6 +206,14 @@ struct SettingsView: View {
|
|||
print("Failed to import file: \(error)")
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Redownload DDI Files?", isPresented: $showDDIConfirmation, titleVisibility: .visible) {
|
||||
Button("Redownload", role: .destructive) {
|
||||
redownloadDDIPressed()
|
||||
}
|
||||
Button("Cancel", role: .cancel) { }
|
||||
} message: {
|
||||
Text("Existing DDI files will be removed before downloading fresh copies.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Cards
|
||||
|
|
@ -358,6 +371,13 @@ struct SettingsView: View {
|
|||
enableTesting = false
|
||||
}
|
||||
}
|
||||
.onChange(of: enableContinuedProcessing) { _, newValue in
|
||||
if newValue {
|
||||
ContinuedProcessingManager.shared.configureIfNeeded()
|
||||
} else {
|
||||
ContinuedProcessingManager.shared.cancelPendingTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -399,7 +419,7 @@ struct SettingsView: View {
|
|||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
Button(action: { redownloadDDIPressed() }) {
|
||||
Button(action: { showDDIConfirmation = true }) {
|
||||
HStack {
|
||||
Image(systemName: "arrow.down.circle")
|
||||
.font(.system(size: 18))
|
||||
|
|
@ -410,6 +430,22 @@ struct SettingsView: View {
|
|||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.disabled(isRedownloadingDDI)
|
||||
|
||||
if isRedownloadingDDI {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
ProgressView(value: ddiDownloadProgress, total: 1.0)
|
||||
.progressViewStyle(.linear)
|
||||
.tint(accentColor)
|
||||
Text(ddiStatusMessage)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
} else if let result = ddiResultMessage {
|
||||
Text(result.text)
|
||||
.font(.caption)
|
||||
.foregroundColor(result.isError ? .red : .green)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -562,7 +598,44 @@ struct SettingsView: View {
|
|||
}
|
||||
|
||||
private func redownloadDDIPressed() {
|
||||
redownloadDDI()
|
||||
guard !isRedownloadingDDI else { return }
|
||||
Task {
|
||||
await MainActor.run {
|
||||
isRedownloadingDDI = true
|
||||
ddiDownloadProgress = 0
|
||||
ddiStatusMessage = "Preparing download…"
|
||||
ddiResultMessage = nil
|
||||
}
|
||||
do {
|
||||
try await redownloadDDI { progress, status in
|
||||
Task { @MainActor in
|
||||
self.ddiDownloadProgress = progress
|
||||
self.ddiStatusMessage = status
|
||||
}
|
||||
}
|
||||
await MainActor.run {
|
||||
isRedownloadingDDI = false
|
||||
ddiResultMessage = ("DDI files refreshed successfully.", false)
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
isRedownloadingDDI = false
|
||||
ddiResultMessage = ("Failed to redownload DDI files: \(error.localizedDescription)", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleDDIStatusDismiss()
|
||||
}
|
||||
|
||||
private func scheduleDDIStatusDismiss() {
|
||||
Task {
|
||||
try? await Task.sleep(nanoseconds: 4_000_000_000)
|
||||
await MainActor.run {
|
||||
if !isRedownloadingDDI {
|
||||
ddiResultMessage = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue