Add watchOS support
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// meterios-watch
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
private var wcMgr: WCManager
|
||||
|
||||
@State private var drinks: Array<Drink> = []
|
||||
|
||||
@State private var showUpdateError: Bool = false
|
||||
@State private var updateError: String = ""
|
||||
@State private var updateErrorCount: Int = 0
|
||||
|
||||
init(_ wcMgr: WCManager) {
|
||||
self.wcMgr = wcMgr
|
||||
}
|
||||
|
||||
// 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 drinks list from paired device
|
||||
wcMgr.requestDrinksList(
|
||||
replyHandler: { drinks in
|
||||
// Drinks received from backend
|
||||
self.drinks = drinks
|
||||
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 drinks.count > 0 {
|
||||
NavigationView {
|
||||
List() {
|
||||
// Drinks
|
||||
Section("Drinks") {
|
||||
ForEach(drinks) { drink in
|
||||
NavigationLink(drink.name) {
|
||||
DrinkDetailView(wcMgr: wcMgr, drink: drink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Controls
|
||||
Section {
|
||||
Button("Refresh") {
|
||||
drinks = []
|
||||
update()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.onAppear(perform: update)
|
||||
// Alert to communicate an error in communication with the paired device
|
||||
.alert("Connection Error", isPresented: $showUpdateError) {
|
||||
Text(updateError).tint(.red).font(.footnote)
|
||||
Button("Retry", systemImage: "arrow.clockwise", action: update)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// DrinkDetailView.swift
|
||||
// meterios-watch
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DrinkDetailView: View {
|
||||
private var wcMgr: WCManager
|
||||
private var drink: Drink
|
||||
|
||||
@State var shouldShowTransactionSheet = false
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
init(wcMgr: WCManager, drink: Drink) {
|
||||
self.wcMgr = wcMgr
|
||||
self.drink = drink
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
// Title
|
||||
Text(drink.name).font(.title)
|
||||
|
||||
// Basic info
|
||||
HStack {
|
||||
VStack {
|
||||
Text(String(format: "%img", drink.caffeine)).fontWeight(.bold)
|
||||
Text("Caffeine").fontWeight(Font.Weight.thin)
|
||||
}
|
||||
Divider()
|
||||
VStack {
|
||||
Text(String(format: "%.2f€", drink.GetPrice())).fontWeight(.bold)
|
||||
Text("Price").fontWeight(Font.Weight.thin)
|
||||
}
|
||||
}
|
||||
|
||||
// Pick button
|
||||
Button(action: {
|
||||
// Open the transaction sheet
|
||||
shouldShowTransactionSheet = true
|
||||
}) {
|
||||
Text("Poison picked!")
|
||||
}.tint(.green)
|
||||
}
|
||||
|
||||
// Transaction sheet
|
||||
.sheet(isPresented: $shouldShowTransactionSheet) {
|
||||
// Sheet dismissed - dismiss detail perspective as well.
|
||||
dismiss()
|
||||
} content: {
|
||||
TransactionView(wcMgr: wcMgr, drinkID: drink.id).interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// TransactionView.swift
|
||||
// meterios-watch
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct TransactionView: View {
|
||||
private var wcMgr: WCManager
|
||||
private var drinkID: Int
|
||||
|
||||
@State var statusLine = "Booking..."
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
// If the backend reacts too fast (we hate that, huh?), the user is confused because of the short life of this view
|
||||
// closeTimer takes care of that confusion and gives the user the good old 56k modem feeling.
|
||||
@State var closeTimer: Timer?
|
||||
@State var closeTimerReady: Bool = false
|
||||
@State var backendBookTimer: Timer?
|
||||
@State var backendTimerReady: Bool = false
|
||||
|
||||
init(wcMgr: WCManager, drinkID: Int) {
|
||||
self.wcMgr = wcMgr
|
||||
self.drinkID = drinkID
|
||||
}
|
||||
|
||||
// maybeDismiss dismisses this view after the backend call(s) finished AND the timer(s) ran out
|
||||
func maybeDismiss() {
|
||||
if closeTimerReady && backendTimerReady {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
// Books the drinks handed over to this view
|
||||
func book() {
|
||||
// Try to book the drink(s)
|
||||
wcMgr.bookDrink(drinkID: drinkID) { salut in
|
||||
statusLine = salut
|
||||
backendTimerReady = true
|
||||
maybeDismiss()
|
||||
} errorHandler: { error in
|
||||
statusLine = error!.localizedDescription
|
||||
print("Error booking drinks: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// Green checkmark or progress indicator
|
||||
VStack {
|
||||
if backendTimerReady {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
// Status line, on success with salute
|
||||
Text(statusLine)
|
||||
.font(.title)
|
||||
|
||||
// Let watch vibrate on success
|
||||
if #available(watchOS 10.0, *) {
|
||||
Text("")
|
||||
.opacity(0)
|
||||
.sensoryFeedback(.success, trigger: backendTimerReady)
|
||||
}
|
||||
}.onAppear(perform: {
|
||||
closeTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) { timer in
|
||||
closeTimerReady = true
|
||||
maybeDismiss()
|
||||
}
|
||||
backendBookTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { timer in
|
||||
book()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user