// // ContentView.swift // meterios-watch // // (c) 2025 Martin "maride" Dessauer // import SwiftUI struct ContentView: View { @EnvironmentObject var wcMgr: WCManager @State private var favouriteDrinks: Array? @State private var availableDrinks: Array? @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) } } }