Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit 3688106

Browse files
authored
I have fixed another bug
1 parent f6327e3 commit 3688106

3 files changed

Lines changed: 86 additions & 49 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Foundation
2+
import Combine
3+
4+
public final class InstallerViewModel: ObservableObject {
5+
public enum InstallerStatus: Equatable {
6+
case idle
7+
case uploading(percent: Int)
8+
case installing(percent: Int)
9+
case success
10+
case failure(message: String)
11+
case message(String)
12+
}
13+
14+
// progress sources (0.0 - 1.0)
15+
@Published public var uploadProgress: Double = 0.0
16+
@Published public var installProgress: Double = 0.0
17+
18+
// semantic status and aggregate progress for UI
19+
@Published public var status: InstallerStatus = .idle
20+
@Published public var progress: Double = 0.0
21+
22+
public init() {}
23+
}
24+
25+
// Helpful human-readable mapping
26+
public extension InstallerViewModel.InstallerStatus {
27+
var pretty: String {
28+
switch self {
29+
case .idle: return ""
30+
case .uploading(let p): return "📦 Uploading: \(p)%"
31+
case .installing(let p): return "📲 Installing: \(p)%"
32+
case .success: return "✅ Installation complete!"
33+
case .failure(let m): return "❌ Install failed: \(m)"
34+
case .message(let m): return m
35+
}
36+
}
37+
}

Sources/prostore/install/installApp.swift

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,32 @@ import Foundation
22
import Combine
33
import IDeviceSwift
44

5-
public func installAppWithStatus(from ipaURL: URL, viewModel: InstallerStatusViewModel) async throws {
5+
public func installAppWithStatus(from ipaURL: URL, viewModel: InstallerViewModel) async throws {
66
var cancellables = Set<AnyCancellable>()
7-
7+
88
viewModel.$uploadProgress
99
.sink { progress in
1010
DispatchQueue.main.async {
11-
viewModel.status = "📦 Uploading: \(Int(progress * 100))%"
11+
let percent = Int(progress * 100)
12+
viewModel.status = .uploading(percent: percent)
1213
viewModel.progress = 0.5 + (progress * 0.25)
1314
}
1415
}
1516
.store(in: &cancellables)
16-
17+
1718
viewModel.$installProgress
1819
.sink { progress in
1920
DispatchQueue.main.async {
20-
viewModel.status = "📲 Installing: \(Int(progress * 100))%"
21+
let percent = Int(progress * 100)
22+
viewModel.status = .installing(percent: percent)
2123
viewModel.progress = 0.75 + (progress * 0.25)
2224
}
2325
}
2426
.store(in: &cancellables)
25-
26-
viewModel.$status
27-
.sink { status in
28-
DispatchQueue.main.async {
29-
viewModel.status = status
30-
}
31-
}
32-
.store(in: &cancellables)
27+
28+
// DON'T reassign viewModel.status inside a sink on viewModel.$status —
29+
// that just creates a loop. If you want to react to status changes elsewhere,
30+
// observe it and map to UI strings there.
3331

3432
let installer = InstallationProxy(viewModel: viewModel)
3533
try await installer.install(at: ipaURL)

Sources/prostore/signing/DownloadSignManager.swift

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -191,45 +191,47 @@ private func signAndInstallIPA(
191191
}
192192

193193
// Create a view model for installation progress
194-
let installerViewModel = InstallerStatusViewModel()
194+
// Create a view model for installation progress
195+
let installerViewModel = InstallerStatusViewModel()
195196

196-
Task {
197-
do {
198-
// Install with status updates using the viewModel
199-
try await installAppWithStatus(from: signedIPAURL, viewModel: installerViewModel)
197+
Task {
198+
do {
199+
try await installAppWithStatus(from: signedIPAURL, viewModel: installerViewModel)
200200

201-
// Observe the viewModel status to update your UI
202-
installerViewModel.$status
203-
.receive(on: DispatchQueue.main)
204-
.sink { status in
205-
switch status {
206-
case .idle: break
207-
case .uploading(let percent), .installing(let percent):
208-
self.progress = Double(percent) / 100.0
209-
self.status = "\(status)"
210-
case .success:
211-
self.status = "✅ Installation complete!"
212-
case .failure(let error):
213-
self.status = "❌ Install failed: \(error)"
214-
}
215-
}
216-
.store(in: &self.cancellables)
217-
218-
// Hide the bar 3 seconds after install is complete
219-
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
220-
self.isProcessing = false
221-
self.showSuccess = false
222-
self.progress = 0.0
223-
self.status = ""
224-
}
225-
226-
} catch {
227-
DispatchQueue.main.async {
228-
self.status = "❌ Install failed: \(error.localizedDescription)"
229-
self.isProcessing = false
230-
}
231-
}
201+
// Observe the viewModel status to update your UI
202+
installerViewModel.$status
203+
.receive(on: DispatchQueue.main)
204+
.sink { status in
205+
switch status {
206+
case .idle:
207+
break
208+
case .uploading(let percent), .installing(let percent):
209+
self.progress = Double(percent) / 100.0
210+
self.status = status.pretty
211+
case .success:
212+
self.status = InstallerStatusViewModel.InstallerStatus.success.pretty
213+
case .failure(let message):
214+
self.status = InstallerStatusViewModel.InstallerStatus.failure(message: message).pretty
215+
case .message(let text):
216+
self.status = text
232217
}
218+
}
219+
.store(in: &self.cancellables)
220+
221+
// Hide the bar 3 seconds after install is complete
222+
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
223+
self.isProcessing = false
224+
self.showSuccess = false
225+
self.progress = 0.0
226+
self.status = ""
227+
}
228+
} catch {
229+
DispatchQueue.main.async {
230+
self.status = "❌ Install failed: \(error.localizedDescription)"
231+
self.isProcessing = false
232+
}
233+
}
234+
}
233235

234236
// Clean up original downloaded IPA
235237
try? FileManager.default.removeItem(at: ipaURL)

0 commit comments

Comments
 (0)