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

Commit a49f70e

Browse files
authored
Add missing stuff
1 parent eae21f2 commit a49f70e

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

Sources/prostore/views/AppsView.swift

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,132 @@ final class RepoViewModel: ObservableObject {
395395
Task { await loadAllSources() }
396396
}
397397
}
398+
// MARK: - RetryAsyncImage (stable layout + retry)
399+
struct RetryAsyncImage<Content: View, Placeholder: View, Failure: View>: View {
400+
let url: URL?
401+
let maxAttempts: Int
402+
let size: CGSize?
403+
let content: (Image) -> Content
404+
let placeholder: () -> Placeholder
405+
let failure: () -> Failure
406+
407+
@State private var currentAttempt: Int = 0
408+
@State private var retryTrigger: UUID = UUID()
409+
410+
private var modifiedURL: URL? {
411+
guard let url = url else { return nil }
412+
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
413+
var query = components?.queryItems ?? []
414+
query.removeAll(where: { $0.name == "retryAttempt" })
415+
query.append(URLQueryItem(name: "retryAttempt", value: "\(currentAttempt)"))
416+
components?.queryItems = query
417+
return components?.url
418+
}
419+
420+
init(
421+
url: URL?,
422+
size: CGSize? = nil,
423+
maxAttempts: Int = 3,
424+
@ViewBuilder content: @escaping (Image) -> Content,
425+
@ViewBuilder placeholder: @escaping () -> Placeholder,
426+
@ViewBuilder failure: @escaping () -> Failure
427+
) {
428+
self.url = url
429+
self.size = size
430+
self.maxAttempts = maxAttempts
431+
self.content = content
432+
self.placeholder = placeholder
433+
self.failure = failure
434+
}
435+
436+
var body: some View {
437+
let frameView = Group {
438+
if let modifiedURL = modifiedURL {
439+
AsyncImage(url: modifiedURL) { phase in
440+
switch phase {
441+
case .empty:
442+
placeholder()
443+
case .success(let image):
444+
content(image)
445+
case .failure:
446+
if currentAttempt < maxAttempts - 1 {
447+
placeholder()
448+
.task {
449+
try? await Task.sleep(nanoseconds: 250_000_000)
450+
await MainActor.run {
451+
currentAttempt += 1
452+
retryTrigger = UUID()
453+
}
454+
}
455+
} else {
456+
failure()
457+
}
458+
@unknown default:
459+
placeholder()
460+
}
461+
}
462+
} else {
463+
failure()
464+
}
465+
}
466+
467+
if let size = size {
468+
frameView
469+
.frame(width: size.width, height: size.height)
470+
.clipped()
471+
} else {
472+
frameView
473+
}
474+
}
475+
}
476+
477+
// Helper for parsing dates
478+
fileprivate func appDate(for app: AltApp) -> Date? {
479+
// Prefer fullDate (format like "20251126100919"), else try versionDate like "2025-11-26"
480+
if let full = app.fullDate {
481+
// try parse yyyyMMddHHmmss or yyyyMMdd
482+
let len = full.count
483+
let formatter = DateFormatter()
484+
formatter.locale = Locale(identifier: "en_US_POSIX")
485+
if len >= 14 {
486+
formatter.dateFormat = "yyyyMMddHHmmss"
487+
} else if len == 8 {
488+
formatter.dateFormat = "yyyyMMdd"
489+
} else {
490+
// fallback attempt
491+
formatter.dateFormat = "yyyyMMddHHmmss"
492+
}
493+
if let d = formatter.date(from: full) { return d }
494+
}
495+
496+
if let vd = app.versionDate {
497+
let formatter = ISO8601DateFormatter()
498+
// attempt strict "yyyy-MM-dd"
499+
if let date = formatter.date(from: vd + "T00:00:00Z") {
500+
return date
501+
}
502+
// fallback try DateFormatter
503+
let df = DateFormatter()
504+
df.locale = Locale(identifier: "en_US_POSIX")
505+
df.dateFormat = "yyyy-MM-dd"
506+
if let d = df.date(from: vd) { return d }
507+
}
508+
509+
return nil
510+
}
511+
512+
// MARK: - Sorting
513+
enum SortOption: String, CaseIterable, Identifiable {
514+
case nameAZ = "Name: A - Z"
515+
case nameZA = "Name: Z - A"
516+
case repoAZ = "Repository"
517+
case dateNewOld = "Date: New - Old"
518+
case dateOldNew = "Date: Old - New"
519+
case sizeLowHigh = "Size: Low - High"
520+
case sizeHighLow = "Size: High - Low"
521+
522+
var id: String { self.rawValue }
523+
}
398524

399525
// MARK: - AppsView (updated to use cached + vm search/sort)
400526
public struct AppsView: View {

0 commit comments

Comments
 (0)