From d3143915eabe5a904d405922c677cecff32fd4c2 Mon Sep 17 00:00:00 2001 From: maride Date: Wed, 23 Apr 2025 23:51:18 +0200 Subject: [PATCH] Cleanup code --- common/AsyncImageWithPlaceholder.swift | 55 +++++++++++++++++++++++++- common/WCManager.swift | 45 +++++---------------- meterios.xcodeproj/project.pbxproj | 1 + meterios/views/DrinkDetailView.swift | 24 +---------- meterios/views/MainView.swift | 42 +++----------------- meterios/views/UserDetailView.swift | 11 +----- 6 files changed, 72 insertions(+), 106 deletions(-) diff --git a/common/AsyncImageWithPlaceholder.swift b/common/AsyncImageWithPlaceholder.swift index c81cea9..3446078 100644 --- a/common/AsyncImageWithPlaceholder.swift +++ b/common/AsyncImageWithPlaceholder.swift @@ -2,7 +2,60 @@ // AsyncImageWithPlaceholder.swift // meterios // -// Created by Maximilian on 23.04.25. +// (c) 2025 Martin "maride" Dessauer // import Foundation +import SwiftUI + +/// **AsyncImageWithPlaceholder** wraps AsyncImage, showing a Spinner while loading and a question mark in case of errors. +struct AsyncImageWithPlaceholder : View { + private var url: URL? + private var width: CGFloat + private var height: CGFloat + + // Access the current color scheme (that is, e.g.: dark mode, light mode) environment value + @Environment(\.colorScheme) var colorScheme + + // baseShape and baseShapeView resemble the "background" (while loading, and in case of errors, and for the image itself) + private var baseShape : some Shape = RoundedRectangle(cornerRadius: 8.0) + var baseShapeView : some View { + baseShape + .foregroundStyle(colorScheme == .dark ? .black : .white) + .frame(width: width, height: height) + } + + /// Creates a new instance of AsyncImageWithPlaceholder, loading the image pointed to by url, with exact measures of width by height + init(url: URL? = nil, width: CGFloat, height: CGFloat) { + self.url = url + self.width = width + self.height = height + } + + var body: some View { + AsyncImage(url: self.url) { phase in + if let image = phase.image { + // Image loaded + image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: self.width, height: self.height) + .clipShape(baseShape) + } else if phase.error != nil { + // Error, display a dummy image (question mark) + ZStack { + baseShapeView + Text("?") + .fontWeight(.bold) + .foregroundStyle(.gray) + } + } else { + // Loading Placeholder + ZStack { + baseShapeView + ProgressView() + } + } + }.fixedSize() + } +} diff --git a/common/WCManager.swift b/common/WCManager.swift index 6cb45e0..ea5a2e2 100644 --- a/common/WCManager.swift +++ b/common/WCManager.swift @@ -9,7 +9,6 @@ import SwiftUI import WatchConnectivity enum WCMeteriosMessageTypes: String { - case ConnectionInfo case DrinkList case BookDrink } @@ -26,7 +25,6 @@ class WCManager: NSObject, WCSessionDelegate { if (WCSession.isSupported()) { session.delegate = self session.activate() - log("Activated session") } } @@ -42,13 +40,8 @@ class WCManager: NSObject, WCSessionDelegate { func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { #if os(iOS) // Check for the different possible message types - if message.contains(where: { $0.key == "request" && $0.value as! String == WCMeteriosMessageTypes.ConnectionInfo.rawValue }) { - // Request for ConnectionInfo - replyHandler([ - "userID": meteUserID, - "hostAddr": meteHostAddr - ]) - } else if message.contains(where: { $0.key == "request" && $0.value as! String == WCMeteriosMessageTypes.DrinkList.rawValue }) { + switch message["request"] as! String { + case WCMeteriosMessageTypes.DrinkList.rawValue: // Request for a list of available drinks Task { let drinks = try await BackendConnector().GetDrinks(baseAddr: meteHostAddr) @@ -65,7 +58,7 @@ class WCManager: NSObject, WCSessionDelegate { "availableDrinks": availableDrinkArray ]) } - } else if message.contains(where: { $0.key == "request" && $0.value as! String == WCMeteriosMessageTypes.BookDrink.rawValue }) { + case WCMeteriosMessageTypes.BookDrink.rawValue: // Book the given drink at the backend Task { do { @@ -83,23 +76,15 @@ class WCManager: NSObject, WCSessionDelegate { ]) } } - } else { - log("Error: received unknown request. \(message)") + default: + // Malformed message from watch + replyHandler([ + "error": "request contains no request", + ]) } #endif // os(iOS) } - // requestConnectionInfo requests User ID and Host address from the paired device - func requestConnectionInfo(replyHandler: @escaping (Int, String) -> Void, errorHandler: @escaping ((any Error)?) -> Void) { - session.sendMessage([ - "request": WCMeteriosMessageTypes.ConnectionInfo.rawValue - ], replyHandler: { reply in - replyHandler(reply["userID"] as! Int, reply["hostAddr"] as! String) - }, errorHandler: { error in - errorHandler(error) - }) - } - // requestDrinksList requests the list of drinks available at the backend from the paired device func requestDrinksList(replyHandler: @escaping (Array, Array) -> Void, errorHandler: @escaping ((any Error)?) -> Void) { session.sendMessage([ @@ -125,21 +110,9 @@ class WCManager: NSObject, WCSessionDelegate { "request": WCMeteriosMessageTypes.BookDrink.rawValue, "drinkID": drinkID, ], replyHandler: { reply in - let salut = reply["salut"] as! String - replyHandler(salut) + replyHandler(reply["salut"] as! String) }, errorHandler: { error in errorHandler(error) }) } - - private func log(_ line: String) { -#if os(iOS) - let prefix = "[WCM@iOS]" -#elseif os(watchOS) - let prefix = "[WCM@wOS]" -#else - let prefix = "[WCM@?OS]" -#endif // os(...) - print("\(prefix) \(line)") - } } diff --git a/meterios.xcodeproj/project.pbxproj b/meterios.xcodeproj/project.pbxproj index ab2a9a2..e66a5ab 100644 --- a/meterios.xcodeproj/project.pbxproj +++ b/meterios.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ A7FA4CCB2D9E9564005ACDBB /* Exceptions for "common" folder in "meterios-watch" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( + AsyncImageWithPlaceholder.swift, Localizable.xcstrings, SalutManager.swift, types/Drink.swift, diff --git a/meterios/views/DrinkDetailView.swift b/meterios/views/DrinkDetailView.swift index 84a2cd6..dc844bd 100644 --- a/meterios/views/DrinkDetailView.swift +++ b/meterios/views/DrinkDetailView.swift @@ -33,29 +33,7 @@ struct DrinkDetail: View { Spacer() VStack() { // Displays the drink image asynchronously - AsyncImage(url: URL(string: "https://\(meteHostAddr)/\(drink.logoURL)")) { phase in - if let image = phase.image { - // Image loaded - image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 300, height: 300) - .clipShape(RoundedRectangle(cornerRadius: 8.0)) - } else if phase.error != nil { - // Error, display a dummy image (question mark) - let _ = Logger().error("Failed to load drink image for \(drink.name): \(phase.error)") - ZStack { - RoundedRectangle(cornerRadius: 8.0).foregroundStyle(.white).tint(.gray).frame(width: 300, height: 300) - Text("?").fontWeight(.bold) - } - } else { - // Loading Placeholder - ZStack { - RoundedRectangle(cornerRadius: 8.0).foregroundStyle(.white).tint(.gray).frame(width: 300, height: 300) - ProgressView() - } - } - }.fixedSize() + AsyncImageWithPlaceholder(url: URL(string: "https://\(meteHostAddr)/\(drink.logoURL)"), width: 300, height: 300).fixedSize() // Basic drink information VStack(alignment: .center) { diff --git a/meterios/views/MainView.swift b/meterios/views/MainView.swift index 81fd2eb..da81864 100644 --- a/meterios/views/MainView.swift +++ b/meterios/views/MainView.swift @@ -15,7 +15,7 @@ struct MainView: View { @State private var favouriteDrinks: Array = [] @State private var availableDrinks: Array = [] @State private var outOfOrderDrinks: Array = [] - @State private var user: User = User.init(meteID: 0, displayName: "", displayImageURL: "", balance: 0.00) + @State private var user: User? @Environment(\.dismiss) var dismiss @@ -23,7 +23,9 @@ struct MainView: View { func update() { Task { do { + // Load user user = try await BackendConnector().GetUser(baseAddr: meteHostAddr, userID: meteUserID) + // Load drinks let drinks = try await BackendConnector().GetDrinks(baseAddr: meteHostAddr) favouriteDrinks = drinks.filter({ FavouritesManager.default.IsFavourite($0.id) && $0.active }) availableDrinks = drinks.filter({ !FavouritesManager.default.IsFavourite($0.id) && $0.active }) @@ -42,29 +44,7 @@ struct MainView: View { update() }) }, label: { - AsyncImage(url: URL(string: "https://\(meteHostAddr)/\(drink.logoURL)")) { phase in - if let image = phase.image { - // Image loaded - image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 32, height: 32) - .clipShape(RoundedRectangle(cornerRadius: 8.0)) - } else if phase.error != nil { - // Error, display a dummy image (question mark) - let _ = Logger().error("Failed to load drink image for \(drink.name): \(phase.error)") - ZStack { - RoundedRectangle(cornerRadius: 8.0).foregroundStyle(.white).tint(.gray).frame(width: 32, height: 32) - Text("?").fontWeight(.bold) - } - } else { - // Loading Placeholder - ZStack { - RoundedRectangle(cornerRadius: 8.0).foregroundStyle(.white).tint(.gray).frame(width: 32, height: 32) - ProgressView() - } - } - }.fixedSize() + AsyncImageWithPlaceholder(url: URL(string: "https://\(meteHostAddr)/\(drink.logoURL)"), width: 32, height: 32) Text(drink.name) }) } @@ -78,23 +58,13 @@ struct MainView: View { List () { // Account Section(header: Text(String(localized: "ACCOUNT")).id("topAnchor")) { - NavigationLink(destination: UserDetailView(user: user)) { + NavigationLink(destination: UserDetailView(user: user!)) { Button(action: { dismiss() }) { HStack { // Profile Image - AsyncImage( - url: URL(string: user.displayImageURL), - content: { image in image.resizable().aspectRatio(contentMode: .fill).frame(width: 64, height: 64).clipShape(Circle()) }, - placeholder: { - ZStack { - Circle().fill(.gray).frame(width: 64, height: 64) - ProgressView() - } - } - ).fixedSize() - + AsyncImageWithPlaceholder(url: URL(string: user!.displayImageURL), width: 64, height: 64).clipShape(Circle()).fixedSize() Spacer(minLength: 16.0) // Account, Address & Balance diff --git a/meterios/views/UserDetailView.swift b/meterios/views/UserDetailView.swift index d335a6a..6cb057d 100644 --- a/meterios/views/UserDetailView.swift +++ b/meterios/views/UserDetailView.swift @@ -22,16 +22,7 @@ struct UserDetailView: View { Spacer() VStack(alignment: .center) { // User image - AsyncImage( - url: URL(string: user.displayImageURL), - content: { image in image.resizable().aspectRatio(contentMode: .fill).frame(width: 256, height: 256).clipShape(Circle()) }, - placeholder: { - ZStack { - Circle().fill(.gray).frame(width: 256, height: 256) - ProgressView() - } - } - ).fixedSize() + AsyncImageWithPlaceholder(url: URL(string: user.displayImageURL), width: 256, height: 256).clipShape(Circle()) // Basic info about user VStack(alignment: .center) {