Transform to multi-account support

This commit is contained in:
2025-08-01 19:31:53 +02:00
parent 351f08e43e
commit 68239727fa
24 changed files with 635 additions and 503 deletions
+43
View File
@@ -0,0 +1,43 @@
//
// AccountButton.swift
// meterios
//
// (c) 2025 Martin "maride" Dessauer
//
import SwiftUI
/// **AccountButton** creates a button showing all relevant information of a Mete user account: profile picture, name, and balance
struct AccountButton : View {
@Bindable private var account: Account
private let action: () -> Void
init(account: Account, action: @escaping () -> Void = {}) {
self.account = account
self.action = action
}
var body: some View {
Button(action: action) {
HStack {
// Profile Image
AsyncImageWithPlaceholder(url: account.displayImageURL, width: 64, height: 64).clipShape(Circle()).fixedSize()
Spacer(minLength: 16.0)
// Account, Address & Balance
VStack(alignment: .leading) {
HStack {
if account.displayName != nil {
Text(account.displayName!).fontWeight(.bold)
}
Divider()
if account.balance != nil {
Text(account.balance!.formatted(.currency(code: "EUR"))).foregroundStyle(account.balance! > 0 ? .green : .red)
}
}.fixedSize()
Text("\(account.address)").fontWeight(.thin)
}.fixedSize()
}.fixedSize()
}
}
}
@@ -0,0 +1,63 @@
//
// AsyncImageWithPlaceholder.swift
// meterios
//
// (c) 2025 Martin "maride" Dessauer
//
import Foundation
import SwiftUI
import OSLog
/// **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, log and display a dummy image (question mark)
let _ = Logger().error("Error loading image: \(phase.error)")
ZStack {
baseShapeView
Text("?")
.fontWeight(.bold)
.foregroundStyle(.gray)
}
} else {
// Loading Placeholder
ZStack {
baseShapeView
ProgressView()
}
}
}.fixedSize()
}
}
+19
View File
@@ -0,0 +1,19 @@
//
// SalutManager.swift
// meterios
//
// (c) 2025 Martin "maride" Dessauer
//
/// **SalutManager** provides random salutes from all over the world.
class SalutManager {
static let salutes: Array<String> = [
"Prost!", "Guten!", "Kippis!", "Cheers!", "Chin-Chin!", "Zum Wohl!", "Salut!", "לחיים",
"Jamas!", // thanks cocorilla
"Budmo!", "за здоровье!", "Sláinte!", "乾杯", "Noroc!", // thanks ChaosAyumi
]
static func getSalute() -> String {
return salutes[Int.random(in: 0..<salutes.count)]
}
}