Cleanup code

This commit is contained in:
2025-04-23 23:51:18 +02:00
parent d6af1a7438
commit d3143915ea
6 changed files with 72 additions and 106 deletions
+54 -1
View File
@@ -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()
}
}
+9 -36
View File
@@ -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<Drink>, Array<Drink>) -> 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)")
}
}
+1
View File
@@ -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,
+1 -23
View File
@@ -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) {
+6 -36
View File
@@ -15,7 +15,7 @@ struct MainView: View {
@State private var favouriteDrinks: Array<Drink> = []
@State private var availableDrinks: Array<Drink> = []
@State private var outOfOrderDrinks: Array<Drink> = []
@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
+1 -10
View File
@@ -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) {