Replace lazy AppStorage calls with AccountManager
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// AccountManager.swift
|
||||
// meterios
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// **AccountManager** stores connection details and the corresponding client
|
||||
class AccountManager: ObservableObject {
|
||||
// default contains a ready-to-use instance of AccountManager, read from AppStorage
|
||||
static public var `default` = AccountManager()
|
||||
|
||||
// accounts holds all saved accounts
|
||||
@Published private(set) var Accounts: Array<AccountManagerEntry> = []
|
||||
|
||||
// init creates a new AccountManager, reading saved account data from AppStorage
|
||||
init() {
|
||||
self.Accounts = AccountManager.readFromRaw()
|
||||
}
|
||||
|
||||
// readFromRaw reads the current accounts from the stored data and transforms it to a handy Account Array
|
||||
static private func readFromRaw() -> Array<AccountManagerEntry> {
|
||||
@AppStorage("accounts") var rawAccs = Data()
|
||||
do {
|
||||
let simpleAccs = try JSONDecoder().decode([Account].self, from: rawAccs)
|
||||
let richAccs = simpleAccs.map({ AccountManagerEntry($0) })
|
||||
return richAccs
|
||||
} catch {
|
||||
print("Error parsing stored accounts from JSON: \(error)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// writeToRaw writes the current account data to the AppStorage
|
||||
private func writeToRaw() {
|
||||
@AppStorage("accounts") var rawAccs = Data()
|
||||
do {
|
||||
let simpleAccs = Accounts.map({ $0.GetAccount() })
|
||||
rawAccs = try JSONEncoder().encode(simpleAccs)
|
||||
} catch {
|
||||
print("Error writing current accounts to JSON: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// CurrentAccount returns the currently selected account if there is one
|
||||
func CurrentAccount() -> AccountManagerEntry? {
|
||||
if self.Accounts.count > 0 {
|
||||
return self.Accounts.first
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAccount adds the given account if it doesn't exist yet
|
||||
func AddAccount(_ account: Account) {
|
||||
if IndexOfAccount(account) == nil {
|
||||
Accounts.append(AccountManagerEntry(account))
|
||||
writeToRaw()
|
||||
}
|
||||
}
|
||||
|
||||
// IndexOfAccount returns the index of the specified account identified by address and user ID
|
||||
func IndexOfAccount(_ account: Account) -> Int? {
|
||||
return Accounts.firstIndex(where: {
|
||||
$0.GetAccount().GetAddress() == account.GetAddress() &&
|
||||
$0.GetAccount().GetUserID() == account.GetUserID()
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAccount removes the account specified by address and user ID of the given Account, if present
|
||||
func RemoveAccount(_ account: Account) {
|
||||
if IndexOfAccount(account) != nil {
|
||||
Accounts.remove(at: IndexOfAccount(account)!)
|
||||
writeToRaw()
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAccount removes the account specified by address and user ID of the given AccountManagerEntry, if present
|
||||
func RemoveAccount(_ account: AccountManagerEntry) {
|
||||
RemoveAccount(account.GetAccount())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// BackendConnector.swift
|
||||
// MeteClient.swift
|
||||
// meterios
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
@@ -7,15 +7,28 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum BackendConnectorError: Error {
|
||||
enum MeteClientError: Error {
|
||||
case StatusCodeLooksBad(explanation: String)
|
||||
}
|
||||
|
||||
class BackendConnector {
|
||||
/// **MeteClient** is the client-side implementation of the Space Market API. Pure Swift, no platform-specific frameworks used.
|
||||
class MeteClient {
|
||||
private let address: String
|
||||
private let userID: Int
|
||||
|
||||
convenience init(account: Account) {
|
||||
self.init(address: account.GetAddress(), userID: account.GetUserID())
|
||||
}
|
||||
|
||||
init(address: String, userID: Int) {
|
||||
self.address = address
|
||||
self.userID = userID
|
||||
}
|
||||
|
||||
// GetDrinks returns the list of drinks available at the backend
|
||||
func GetDrinks(baseAddr: String) async throws -> Array<Drink> {
|
||||
func GetDrinks() async throws -> Array<Drink> {
|
||||
// Send HTTP request to the drinks endpoint
|
||||
let url = URL(string: "https://\(baseAddr)/api/v1/drinks")
|
||||
let url = URL(string: "https://\(address)/api/v1/drinks")
|
||||
let (data, _) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Decode JSON to array of drinks
|
||||
@@ -25,10 +38,10 @@ class BackendConnector {
|
||||
return drinks
|
||||
}
|
||||
|
||||
// GetUser returns the user object for the given ID<
|
||||
func GetUser(baseAddr: String, userID: Int) async throws -> User {
|
||||
// GetUser returns the user object for the given ID
|
||||
func GetUser() async throws -> User {
|
||||
// Send HTTP request to the user endpoint
|
||||
let url = URL(string: "https://\(baseAddr)/api/v1/users/\(userID).json")
|
||||
let url = URL(string: "https://\(address)/api/v1/users/\(userID).json")
|
||||
let (data, _) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Decode JSON to array of drinks
|
||||
@@ -39,9 +52,9 @@ class BackendConnector {
|
||||
}
|
||||
|
||||
// GetUsers returns all users registered at the backend
|
||||
func GetUsers(baseAddr: String) async throws -> Array<User> {
|
||||
func GetUsers() async throws -> Array<User> {
|
||||
// Send HTTP request to the user endpoint
|
||||
let url = URL(string: "https://\(baseAddr)/api/v1/users.json")
|
||||
let url = URL(string: "https://\(address)/api/v1/users.json")
|
||||
let (data, _) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Decode JSON to array of drinks
|
||||
@@ -52,16 +65,15 @@ class BackendConnector {
|
||||
}
|
||||
|
||||
// BookDrink subtracts the recommended price off the balance from the user given by ID
|
||||
func BookDrink(baseAddr: String, userID: Int, drinkID: Int) async throws {
|
||||
func BookDrink(_ drinkID: Int) async throws {
|
||||
// Send HTTP request to the user endpoint
|
||||
let url = URL(string: "https://\(baseAddr)/api/v1/users/\(userID)/buy.json?drink=\(drinkID)")
|
||||
let url = URL(string: "https://\(address)/api/v1/users/\(userID)/buy.json?drink=\(drinkID)")
|
||||
let (_, response) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Check status code
|
||||
let statusCode = (response as? HTTPURLResponse)?.statusCode
|
||||
if statusCode != 204 {
|
||||
throw BackendConnectorError.StatusCodeLooksBad(explanation: "Failed to book drink \(drinkID) for user \(userID)@\(baseAddr): HTTP status code \(statusCode) received for \(url)")
|
||||
throw MeteClientError.StatusCodeLooksBad(explanation: "Failed to book drink \(drinkID) for user \(userID)@\(address): HTTP status code \(statusCode) received for \(url)")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+12
-6
@@ -13,13 +13,13 @@ enum WCMeteriosMessageTypes: String {
|
||||
case BookDrink
|
||||
}
|
||||
|
||||
class WCManager: NSObject, WCSessionDelegate {
|
||||
@AppStorage("meteHostAddr") private var meteHostAddr = ""
|
||||
@AppStorage("meteUserID") private var meteUserID = -1
|
||||
|
||||
/// **WCManager** managed the Meterios-specific communication between an iOS and an watchOS device
|
||||
class WCManager: NSObject, WCSessionDelegate, ObservableObject {
|
||||
private var accountManager: AccountManager
|
||||
private let session: WCSession = WCSession.default
|
||||
|
||||
override init() {
|
||||
self.accountManager = AccountManager.default
|
||||
super.init()
|
||||
|
||||
if (WCSession.isSupported()) {
|
||||
@@ -39,12 +39,18 @@ class WCManager: NSObject, WCSessionDelegate {
|
||||
// As Meterios currently only needs messages from watchOS to iOS and responses back to the watch, this function currently only implements iOS-related responses.
|
||||
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
|
||||
#if os(iOS)
|
||||
// Prevent crashes when WCSession is received before AccountManager is ready
|
||||
if accountManager.CurrentAccount() == nil {
|
||||
print("Request encountered in WCSession, but AccountManager is not ready.")
|
||||
return
|
||||
}
|
||||
|
||||
// Check for the different possible message types
|
||||
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)
|
||||
let drinks = try await accountManager.CurrentAccount()!.GetClient().GetDrinks()
|
||||
let favouriteDrinks = drinks.filter({ FavouritesManager.default.IsFavourite($0.id) && $0.active })
|
||||
let availableDrinks = drinks.filter({ !FavouritesManager.default.IsFavourite($0.id) && $0.active })
|
||||
// note that drinks which are not active/available are not sent to the watch - the screen is too small to waste pixels.
|
||||
@@ -64,7 +70,7 @@ class WCManager: NSObject, WCSessionDelegate {
|
||||
do {
|
||||
// Book
|
||||
let drinkID = message["drinkID"] as! Int
|
||||
try await BackendConnector().BookDrink(baseAddr: meteHostAddr, userID: meteUserID, drinkID: drinkID)
|
||||
try await accountManager.CurrentAccount()!.GetClient().BookDrink(drinkID)
|
||||
replyHandler([
|
||||
"salut": SalutManager.getSalute()
|
||||
])
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Account.swift
|
||||
// meterios
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
struct Account: Encodable, Decodable {
|
||||
private let address: String
|
||||
private let userID: Int
|
||||
|
||||
init(address: String, userID: Int) {
|
||||
self.address = address
|
||||
self.userID = userID
|
||||
}
|
||||
|
||||
func GetAddress() -> String {
|
||||
return address
|
||||
}
|
||||
|
||||
func GetUserID() -> Int {
|
||||
return userID
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// AccountManagerEntry.swift
|
||||
// meterios
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
struct AccountManagerEntry {
|
||||
private let account: Account
|
||||
private let client: MeteClient
|
||||
|
||||
init(_ account: Account) {
|
||||
self.account = account
|
||||
self.client = MeteClient(account: account)
|
||||
}
|
||||
|
||||
func GetAccount() -> Account {
|
||||
return account
|
||||
}
|
||||
|
||||
func GetClient() -> MeteClient {
|
||||
return client
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct meteriosWatchApp: App {
|
||||
private var wcMgr = WCManager()
|
||||
@StateObject var wcMgr = WCManager()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(wcMgr)
|
||||
ContentView().environmentObject(wcMgr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
private var wcMgr: WCManager
|
||||
@EnvironmentObject var wcMgr: WCManager
|
||||
|
||||
@State private var favouriteDrinks: Array<Drink> = []
|
||||
@State private var availableDrinks: Array<Drink> = []
|
||||
@@ -17,10 +17,6 @@ struct ContentView: View {
|
||||
@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 {
|
||||
@@ -55,14 +51,14 @@ struct ContentView: View {
|
||||
Section(String(localized: "FAVOURITES")) {
|
||||
ForEach(favouriteDrinks) { drink in
|
||||
NavigationLink(drink.name) {
|
||||
DrinkDetailView(wcMgr: wcMgr, drink: drink)
|
||||
DrinkDetailView(drink: drink)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section(String(localized: "DRINKS")) {
|
||||
ForEach(availableDrinks) { drink in
|
||||
NavigationLink(drink.name) {
|
||||
DrinkDetailView(wcMgr: wcMgr, drink: drink)
|
||||
DrinkDetailView(drink: drink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
import SwiftUI
|
||||
|
||||
struct DrinkDetailView: View {
|
||||
private var wcMgr: WCManager
|
||||
@EnvironmentObject var wcMgr: WCManager
|
||||
|
||||
private var drink: Drink
|
||||
|
||||
@State var shouldShowTransactionSheet = false
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
init(wcMgr: WCManager, drink: Drink) {
|
||||
self.wcMgr = wcMgr
|
||||
init(drink: Drink) {
|
||||
self.drink = drink
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ struct DrinkDetailView: View {
|
||||
// Sheet dismissed - dismiss detail perspective as well.
|
||||
dismiss()
|
||||
} content: {
|
||||
TransactionView(wcMgr: wcMgr, drinkID: drink.id).interactiveDismissDisabled()
|
||||
TransactionView(drinkID: drink.id).interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TransactionView: View {
|
||||
private var wcMgr: WCManager
|
||||
@EnvironmentObject var wcMgr: WCManager
|
||||
|
||||
private var drinkID: Int
|
||||
|
||||
@State var statusLine = String(localized: "BOOKING")
|
||||
@@ -22,8 +23,7 @@ struct TransactionView: View {
|
||||
@State var backendBookTimer: Timer?
|
||||
@State var backendTimerReady: Bool = false
|
||||
|
||||
init(wcMgr: WCManager, drinkID: Int) {
|
||||
self.wcMgr = wcMgr
|
||||
init(drinkID: Int) {
|
||||
self.drinkID = drinkID
|
||||
}
|
||||
|
||||
|
||||
@@ -40,19 +40,16 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
A7FA4CBE2D9E90B0005ACDBB /* Exceptions for "meterios" folder in "meterios-watch" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
BackendConnector.swift,
|
||||
);
|
||||
target = A7FA4CA92D9E8FF1005ACDBB /* meterios-watch */;
|
||||
};
|
||||
A7FA4CCB2D9E9564005ACDBB /* Exceptions for "common" folder in "meterios-watch" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
AccountManager.swift,
|
||||
AsyncImageWithPlaceholder.swift,
|
||||
Localizable.xcstrings,
|
||||
MeteClient.swift,
|
||||
SalutManager.swift,
|
||||
types/Account.swift,
|
||||
types/AccountManagerEntry.swift,
|
||||
types/Drink.swift,
|
||||
types/User.swift,
|
||||
WCManager.swift,
|
||||
@@ -64,9 +61,6 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
A7F97A902D8DD542004EF4ED /* meterios */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
A7FA4CBE2D9E90B0005ACDBB /* Exceptions for "meterios" folder in "meterios-watch" target */,
|
||||
);
|
||||
path = meterios;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
@@ -9,17 +9,14 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct meteriosApp: App {
|
||||
@AppStorage("meteHostAddr") private var meteHostAddr = ""
|
||||
@AppStorage("meteUserID") private var meteUserID = -1
|
||||
|
||||
private var wcMgr = WCManager()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if meteHostAddr == "" || meteUserID == -1 {
|
||||
SetupView()
|
||||
} else {
|
||||
if AccountManager.default.CurrentAccount() != nil {
|
||||
MainView()
|
||||
} else {
|
||||
SetupView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ import SwiftUI
|
||||
import OSLog
|
||||
|
||||
struct DrinkDetail: View {
|
||||
@AppStorage("meteHostAddr") private var meteHostAddr = ""
|
||||
@AppStorage("meteUserID") private var meteUserID = -1
|
||||
|
||||
private var drink: Drink
|
||||
@State private var drinkAmount: Int = 1
|
||||
@State var shouldShowTransactionSheet = false
|
||||
@@ -26,6 +23,7 @@ struct DrinkDetail: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if AccountManager.default.CurrentAccount() != nil {
|
||||
List() {
|
||||
// Drink details
|
||||
Section() {
|
||||
@@ -33,7 +31,7 @@ struct DrinkDetail: View {
|
||||
Spacer()
|
||||
VStack() {
|
||||
// Displays the drink image asynchronously
|
||||
AsyncImageWithPlaceholder(url: URL(string: "https://\(meteHostAddr)/\(drink.logoURL)"), width: 300, height: 300).fixedSize()
|
||||
AsyncImageWithPlaceholder(url: URL(string: "https://\(AccountManager.default.CurrentAccount()!.GetAccount().GetAddress())/\(drink.logoURL)"), width: 300, height: 300).fixedSize()
|
||||
|
||||
// Basic drink information
|
||||
VStack(alignment: .center) {
|
||||
@@ -111,3 +109,4 @@ struct DrinkDetail: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ import SwiftUI
|
||||
import OSLog
|
||||
|
||||
struct MainView: View {
|
||||
@AppStorage("meteHostAddr") private var meteHostAddr = ""
|
||||
@AppStorage("meteUserID") private var meteUserID = -1
|
||||
|
||||
@State private var favouriteDrinks: Array<Drink> = []
|
||||
@State private var availableDrinks: Array<Drink> = []
|
||||
@State private var outOfOrderDrinks: Array<Drink> = []
|
||||
@@ -24,14 +21,14 @@ struct MainView: View {
|
||||
Task {
|
||||
do {
|
||||
// Load user
|
||||
user = try await BackendConnector().GetUser(baseAddr: meteHostAddr, userID: meteUserID)
|
||||
user = try await AccountManager.default.CurrentAccount()!.GetClient().GetUser()
|
||||
// Load drinks
|
||||
let drinks = try await BackendConnector().GetDrinks(baseAddr: meteHostAddr)
|
||||
let drinks = try await AccountManager.default.CurrentAccount()!.GetClient().GetDrinks()
|
||||
favouriteDrinks = drinks.filter({ FavouritesManager.default.IsFavourite($0.id) && $0.active })
|
||||
availableDrinks = drinks.filter({ !FavouritesManager.default.IsFavourite($0.id) && $0.active })
|
||||
outOfOrderDrinks = drinks.filter({ !$0.active })
|
||||
} catch {
|
||||
let _ = Logger().error("Failed to update from \(meteHostAddr): \(error)")
|
||||
let _ = Logger().error("Failed to update from \(AccountManager.default.CurrentAccount()!.GetAccount().GetAddress()): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +41,7 @@ struct MainView: View {
|
||||
update()
|
||||
})
|
||||
}, label: {
|
||||
AsyncImageWithPlaceholder(url: URL(string: "https://\(meteHostAddr)/\(drink.logoURL)"), width: 32, height: 32)
|
||||
AsyncImageWithPlaceholder(url: URL(string: "https://\(AccountManager.default.CurrentAccount()!.GetAccount().GetAddress())/\(drink.logoURL)"), width: 32, height: 32)
|
||||
Text(drink.name)
|
||||
})
|
||||
}
|
||||
@@ -52,11 +49,11 @@ struct MainView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { scrollViewReader in
|
||||
if AccountManager.default.CurrentAccount() != nil {
|
||||
NavigationView {
|
||||
VStack(alignment: .leading) {
|
||||
List () {
|
||||
// Account
|
||||
if user != nil {
|
||||
Section(header: Text(String(localized: "ACCOUNT")).id("topAnchor")) {
|
||||
NavigationLink(destination: UserDetailView(user: user!)) {
|
||||
Button(action: {
|
||||
@@ -70,16 +67,17 @@ struct MainView: View {
|
||||
// Account, Address & Balance
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text(user.displayName).fontWeight(.bold)
|
||||
Text(user!.displayName).fontWeight(.bold)
|
||||
Divider()
|
||||
Text(user.balance.formatted(.currency(code: "EUR"))).foregroundStyle(user.balance > 0 ? .green : .red)
|
||||
Text(user!.balance.formatted(.currency(code: "EUR"))).foregroundStyle(user!.balance > 0 ? .green : .red)
|
||||
}.fixedSize()
|
||||
Text("\(meteHostAddr)").fontWeight(.thin)
|
||||
Text("\(AccountManager.default.CurrentAccount()!.GetAccount().GetAddress())").fontWeight(.thin)
|
||||
}.fixedSize()
|
||||
}.fixedSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drinks list
|
||||
if !favouriteDrinks.isEmpty {
|
||||
@@ -108,7 +106,6 @@ struct MainView: View {
|
||||
}.navigationTitle(String(localized: "METERIOS")).refreshable(action: {
|
||||
update()
|
||||
})
|
||||
}
|
||||
}.onAppear(perform: {
|
||||
update()
|
||||
})
|
||||
|
||||
@@ -9,9 +9,6 @@ import SwiftUI
|
||||
import OSLog
|
||||
|
||||
struct SetupView: View {
|
||||
@AppStorage("meteHostAddr") private var meteHostAddr = ""
|
||||
@AppStorage("meteUserID") private var meteUserID = -1
|
||||
|
||||
@State private var tmpAddress: String = "mete.chaosdorf.space"
|
||||
@State private var tmpUsers: Array<User> = []
|
||||
@State private var selectedUserID: Int = 0
|
||||
@@ -28,12 +25,6 @@ struct SetupView: View {
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
init() {
|
||||
if meteHostAddr != "" {
|
||||
self.tmpAddress = meteHostAddr
|
||||
}
|
||||
}
|
||||
|
||||
func tryConnect() {
|
||||
// If there was a previous connection attempt, the user list and save button might still be visible - hide them again
|
||||
userListVisible = false
|
||||
@@ -54,7 +45,8 @@ struct SetupView: View {
|
||||
// Create test connector
|
||||
do {
|
||||
// Try out the connection
|
||||
tmpUsers = try await BackendConnector().GetUsers(baseAddr: tmpAddress)
|
||||
let tmpConnector = MeteClient(address: tmpAddress, userID: -1)
|
||||
tmpUsers = try await tmpConnector.GetUsers()
|
||||
connectionStatus = String(localized: "CONNECTION_SUCCESSFUL")
|
||||
connectionStatusIcon = "checkmark.icloud.fill"
|
||||
connectionStatusColor = .green
|
||||
@@ -101,7 +93,7 @@ struct SetupView: View {
|
||||
// Connection
|
||||
Section(content: {
|
||||
// Address field
|
||||
TextField(text: $tmpAddress, label: { Text(meteHostAddr) })
|
||||
TextField(text: $tmpAddress, label: { Text(tmpAddress) })
|
||||
.textInputAutocapitalization(.never)
|
||||
.disableAutocorrection(true)
|
||||
.focused($connectionFiledFocused)
|
||||
@@ -146,8 +138,7 @@ struct SetupView: View {
|
||||
// Save button
|
||||
Section {
|
||||
Button(action: {
|
||||
meteHostAddr = tmpAddress
|
||||
meteUserID = selectedUserID
|
||||
AccountManager.default.AddAccount(Account(address: tmpAddress, userID: selectedUserID))
|
||||
dismiss()
|
||||
}) {
|
||||
Text(String(localized: "SAVE_CONNECTION_SETTINGS")).frame(maxWidth: .infinity)
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TransactionView: View {
|
||||
@AppStorage("meteHostAddr") private var meteHostAddr = ""
|
||||
@AppStorage("meteUserID") private var meteUserID = -1
|
||||
|
||||
var drinkID: Int
|
||||
var drinkAmount: Int
|
||||
|
||||
@@ -43,7 +40,7 @@ struct TransactionView: View {
|
||||
do {
|
||||
// Try to book the drink(s)
|
||||
for _ in 1...drinkAmount {
|
||||
try await BackendConnector().BookDrink(baseAddr: meteHostAddr, userID: meteUserID, drinkID: drinkID)
|
||||
try await AccountManager.default.CurrentAccount()!.GetClient().BookDrink(drinkID)
|
||||
}
|
||||
statusLine = SalutManager.getSalute()
|
||||
backendTimerReady = true
|
||||
@@ -57,6 +54,7 @@ struct TransactionView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if AccountManager.default.CurrentAccount() != nil {
|
||||
// Green checkmark or progress indicator
|
||||
if backendTimerReady {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
@@ -81,3 +79,4 @@ struct TransactionView: View {
|
||||
.sensoryFeedback(.success, trigger: backendTimerReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
import SwiftUI
|
||||
|
||||
struct UserDetailView: View {
|
||||
@AppStorage("meteHostAddr") private var meteHostAddr = ""
|
||||
@AppStorage("meteUserID") private var meteUserID = -1
|
||||
|
||||
var user: User
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
if AccountManager.default.CurrentAccount() != nil {
|
||||
List() {
|
||||
Section {
|
||||
HStack(alignment: .center) {
|
||||
@@ -37,7 +35,7 @@ struct UserDetailView: View {
|
||||
}.fixedSize()
|
||||
Divider()
|
||||
VStack {
|
||||
Text(String(meteHostAddr))
|
||||
Text(String(AccountManager.default.CurrentAccount()!.GetAccount().GetAddress()))
|
||||
Text(String(localized: "METE_SERVER")).fontWeight(Font.Weight.thin)
|
||||
}
|
||||
}
|
||||
@@ -50,7 +48,7 @@ struct UserDetailView: View {
|
||||
// Open profile in browser
|
||||
Section() {
|
||||
Button(action: {
|
||||
UIApplication.shared.open(URL(string: "https://\(meteHostAddr)/users/\(meteUserID)")!)
|
||||
UIApplication.shared.open(URL(string: "https://\(AccountManager.default.CurrentAccount()!.GetAccount().GetAddress())/users/\(AccountManager.default.CurrentAccount()!.GetAccount().GetUserID())")!)
|
||||
}) {
|
||||
Text(String(localized: "OPEN_PROFILE_IN_BROWSER")).frame(maxWidth: .infinity)
|
||||
}
|
||||
@@ -61,7 +59,7 @@ struct UserDetailView: View {
|
||||
Button(action: {
|
||||
// Log out. Save the User ID as -1, which is forbidden by the backend.
|
||||
// This forces Meterios to display the setup screen.
|
||||
meteUserID = -1
|
||||
AccountManager.default.RemoveAccount(AccountManager.default.CurrentAccount()!)
|
||||
dismiss()
|
||||
}) {
|
||||
Text(String(localized: "LOGOUT")).frame(maxWidth: .infinity)
|
||||
@@ -70,3 +68,4 @@ struct UserDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user