From fb103fbabd9865f711f5052f63816b35fc259585 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 1 Apr 2026 23:54:01 -0500 Subject: feat: expand user profile in Look Up --- Hutch/Models/User.swift | 9 ++++++ Hutch/Views/Lookup/LookupView.swift | 47 ++++++++++++++++++++++++----- Hutch/Views/Lookup/UserProfileView.swift | 52 +++++++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/Hutch/Models/User.swift b/Hutch/Models/User.swift index a9350a3..625cfdc 100644 --- a/Hutch/Models/User.swift +++ b/Hutch/Models/User.swift @@ -3,8 +3,17 @@ import Foundation /// A Sourcehut user from meta.sr.ht. struct User: Decodable, Sendable { let id: Int + let created: String? + let updated: String? let username: String let canonicalName: String let email: String + let url: String? + let location: String? + let bio: String? let avatar: String? + let pronouns: String? + let userType: String? + let receivesPaidServices: Bool? + let suspensionNotice: String? } diff --git a/Hutch/Views/Lookup/LookupView.swift b/Hutch/Views/Lookup/LookupView.swift index 9f42d9e..413ea12 100644 --- a/Hutch/Views/Lookup/LookupView.swift +++ b/Hutch/Views/Lookup/LookupView.swift @@ -1,4 +1,7 @@ import SwiftUI +import os + +private let lookupLogger = Logger(subsystem: "net.cleberg.Hutch", category: "Lookup") enum LookupType: String, CaseIterable, Identifiable { case user = "User" @@ -166,17 +169,47 @@ final class LookupViewModel { let query = """ query userLookup($username: String!) { user: userByName(username: $username) { - id username canonicalName email avatar + id + created + updated + canonicalName + username + email + url + location + bio + avatar + pronouns + userType } } """ - let result = try await client.execute( - service: .meta, - query: query, - variables: ["username": username], - responseType: Response.self - ) + lookupLogger.debug("Looking up user profile for username: \(username, privacy: .public)") + + let result: Response + do { + result = try await client.execute( + service: .meta, + query: query, + variables: ["username": username], + responseType: Response.self + ) + } catch { + lookupLogger.error( + """ + User lookup failed + username: \(username, privacy: .public) + query: + \(query, privacy: .public) + error: + \(String(describing: error), privacy: .public) + """ + ) + throw error + } + + lookupLogger.debug("User lookup succeeded for username: \(username, privacy: .public)") return .user(result.user) } diff --git a/Hutch/Views/Lookup/UserProfileView.swift b/Hutch/Views/Lookup/UserProfileView.swift index 3fcbb2f..4c91244 100644 --- a/Hutch/Views/Lookup/UserProfileView.swift +++ b/Hutch/Views/Lookup/UserProfileView.swift @@ -3,7 +3,12 @@ import SwiftUI struct UserProfileView: View { let user: User - @Environment(AppState.self) private var appState + private static let iso8601Formatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter + }() var body: some View { List { @@ -42,11 +47,56 @@ struct UserProfileView: View { Section { LabeledContent("Username", value: user.username) LabeledContent("Canonical Name", value: user.canonicalName) + if let userType = user.userType { + LabeledContent("User Type", value: userType) + } + if let pronouns = user.pronouns { + LabeledContent("Pronouns", value: pronouns) + } + if let suspensionNotice = user.suspensionNotice { + LabeledContent("Suspension Notice", value: suspensionNotice) + } + } + + Section { LabeledContent("Email", value: user.email) + if let urlString = user.url, let url = URL(string: urlString) { + LabeledContent("URL") { + Link(urlString, destination: url) + } + } + if let location = user.location { + LabeledContent("Location", value: location) + } + } + + if let bio = user.bio { + Section("Bio") { + Text(bio) + } + } + + if user.created != nil || user.updated != nil { + Section { + if let created = user.created { + LabeledContent("Joined", value: formattedTimestamp(created)) + } + if let updated = user.updated { + LabeledContent("Updated", value: formattedTimestamp(updated)) + } + } } } .listStyle(.insetGrouped) .navigationTitle(user.canonicalName) .navigationBarTitleDisplayMode(.inline) } + + private func formattedTimestamp(_ value: String) -> String { + guard let date = Self.iso8601Formatter.date(from: value) else { + return value + } + + return date.formatted(date: .abbreviated, time: .shortened) + } } -- cgit v1.2.3