payfrit-beacon-ios/PayfritBeacon/Views/BusinessListView.swift
Schwifty 2496cab7f3 fix: wrap iOS 17+ APIs in @available checks for iOS 16 compat
ContentUnavailableView and .symbolEffect(.pulse) require iOS 17+
but deployment target is iOS 16. Wrapped all usages in
if #available(iOS 17.0, *) with VStack-based fallbacks for iOS 16.

Files fixed:
- ScanView.swift (4 ContentUnavailableView + 1 symbolEffect)
- QRScannerView.swift (1 ContentUnavailableView)
- BusinessListView.swift (2 ContentUnavailableView)
2026-03-22 19:20:46 +00:00

152 lines
5.4 KiB
Swift

import SwiftUI
/// Business selector screen (matches Android MainActivity)
struct BusinessListView: View {
@EnvironmentObject var appState: AppState
@State private var businesses: [Business] = []
@State private var isLoading = true
@State private var errorMessage: String?
var body: some View {
NavigationStack {
Group {
if isLoading {
ProgressView("Loading businesses…")
} else if let error = errorMessage {
if #available(iOS 17.0, *) {
ContentUnavailableView {
Label("Error", systemImage: "exclamationmark.triangle")
} description: {
Text(error)
} actions: {
Button("Retry") { Task { await loadBusinesses() } }
}
} else {
VStack(spacing: 12) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 48))
.foregroundStyle(.orange)
Text("Error")
.font(.headline)
Text(error)
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Button("Retry") { Task { await loadBusinesses() } }
.buttonStyle(.borderedProminent)
}
.padding()
}
} else if businesses.isEmpty {
if #available(iOS 17.0, *) {
ContentUnavailableView(
"No Businesses",
systemImage: "building.2",
description: Text("You don't have any businesses yet.")
)
} else {
VStack(spacing: 12) {
Image(systemName: "building.2")
.font(.system(size: 48))
.foregroundStyle(.secondary)
Text("No Businesses")
.font(.headline)
Text("You don't have any businesses yet.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.frame(maxHeight: .infinity)
.padding()
}
} else {
List(businesses) { business in
Button {
appState.selectBusiness(business)
} label: {
BusinessRow(business: business)
}
}
.listStyle(.insetGrouped)
}
}
.navigationTitle("Select Business")
.toolbar(content: {
ToolbarItem(placement: .topBarTrailing) {
Button("Logout") {
appState.logout()
}
}
})
}
.task {
await loadBusinesses()
}
}
private func loadBusinesses() async {
guard let token = appState.token else { return }
isLoading = true
errorMessage = nil
do {
let list = try await APIClient.shared.listBusinesses(token: token)
businesses = list
// Auto-navigate if only one business (like Android)
if list.count == 1, let only = list.first {
appState.selectBusiness(only)
return
}
// Auto-navigate to last used business
if let lastId = AppPrefs.lastBusinessId,
let last = list.first(where: { $0.id == lastId }) {
appState.selectBusiness(last)
return
}
} catch let e as APIError where e.errorDescription == APIError.unauthorized.errorDescription {
appState.logout()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
// MARK: - Business Row
struct BusinessRow: View {
let business: Business
var body: some View {
HStack(spacing: 12) {
// Business header image
AsyncImage(url: business.headerImageURL) { image in
image.resizable().aspectRatio(contentMode: .fill)
} placeholder: {
Image(systemName: "building.2")
.font(.title2)
.foregroundStyle(.secondary)
}
.frame(width: 56, height: 56)
.clipShape(RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading, spacing: 4) {
Text(business.name)
.font(.headline)
Text("ID: \(business.id)")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Image(systemName: "chevron.right")
.foregroundStyle(.tertiary)
}
.padding(.vertical, 4)
}
}