Files

134 lines
5.5 KiB
Swift

//
// ContentView.swift
// meterios-watch
//
// (c) 2025 Martin "maride" Dessauer
//
import SwiftUI
struct ContentView: View {
@EnvironmentObject var wcMgr: WCManager
@State private var favouriteDrinks: Array<MeteClientDrink>?
@State private var availableDrinks: Array<MeteClientDrink>?
@State private var userInfo: WCUser?
@State private var showUpdateError: Bool = false
@State private var updateError: String = ""
@State private var updateErrorCount: Int = 0
// update performs a full update from the paired device, covering connection info (user ID and backend address) and list of drinks
func update() {
if updateErrorCount >= 3 {
showUpdateError = true
updateErrorCount = 0
return
}
// Update user info
wcMgr.requestUserInfo(replyHandler: { userInfo in
self.userInfo = userInfo
}, errorHandler: { error in
// Error receiving user info
print("Error receiving user info: \(error)")
})
// Update drinks list from paired device
wcMgr.requestDrinksList(
replyHandler: { favDrinks, availDrinks in
// Drinks received from backend
self.favouriteDrinks = favDrinks
self.availableDrinks = availDrinks
showUpdateError = false
}, errorHandler: { error in
// Error receiving drinks
print("Error receiving drinks: \(error)")
updateError = error?.localizedDescription ?? "Unknown error"
updateErrorCount += 1
update()
}
)
}
var body: some View {
ZStack {
if favouriteDrinks != nil && availableDrinks != nil {
NavigationView {
List() {
// Account
if userInfo != nil {
Button(action: {}, label: {
HStack {
// Profile Image
AsyncImageWithPlaceholder(url: userInfo?.displayImageURL, width: 64, height: 64).clipShape(Circle()).fixedSize()
Spacer(minLength: 16.0)
// Account, Address & Balance
VStack(alignment: .leading) {
Text(userInfo!.displayName)
Text((userInfo!.balance!.formatted(.currency(code: "EUR")))).foregroundStyle((userInfo?.balance)! > 0 ? .green : .red)
}.fixedSize()
}.fixedSize()
})
}
// Drinks
if !favouriteDrinks!.isEmpty {
Section(String(localized: "FAVOURITES")) {
ForEach(favouriteDrinks!) { drink in
NavigationLink(destination: {
DrinkDetailView(drink: drink)
}, label: {
AsyncImageWithPlaceholder(url: URL(string: "https://\(account.address)/\(drink.logoURL)"), width: 32, height: 32)
Text(drink.name)
})
}
}
}
if !availableDrinks!.isEmpty {
Section(String(localized: "DRINKS")) {
ForEach(availableDrinks!) { drink in
NavigationLink(drink.name) {
DrinkDetailView(drink: drink)
}
}
}
} else {
Section(content: {}, footer: {
HStack(alignment: .center, content: {
Spacer()
VStack() {
Image(systemName: "bolt.horizontal.circle.fill").font(.title).foregroundStyle(.gray)
Spacer(minLength: 8)
Text(String(localized: "NO_DRINKS")).font(.title).foregroundStyle(.gray)
}
Spacer()
})
})
}
// Controls
Section {
Button(String(localized: "REFRESH")) {
favouriteDrinks = []
availableDrinks = []
update()
}
}
}
}
} else {
ProgressView()
}
}
.onAppear(perform: update)
// Alert to communicate an error in communication with the paired device
.alert(String(localized: "CONNECTION_ERROR"), isPresented: $showUpdateError) {
Text(updateError).tint(.red).font(.footnote)
Button(String(localized: "RETRY"), systemImage: "arrow.clockwise", action: update)
}
}
}