Commit fd4d8f49e0

fd4d8f49e05054c05722cb16da555943d4007736

parent: c619fe4a96

Unsigned

cmc <hello@cleberg.net> · 2026-04-02 04:54 UTC

feat: expand user profile in Look Up

Layout: unified · split

Hutch/Models/User.swift +9
@@ -3,8 +3,17 @@ import Foundation
3/// A Sourcehut user from meta.sr.ht. 3/// A Sourcehut user from meta.sr.ht.
4struct User: Decodable, Sendable { 4struct User: Decodable, Sendable {
5 let id: Int 5 let id: Int
6 let created: String?
7 let updated: String?
6 let username: String 8 let username: String
7 let canonicalName: String 9 let canonicalName: String
8 let email: String 10 let email: String
11 let url: String?
12 let location: String?
13 let bio: String?
9 let avatar: String? 14 let avatar: String?
15 let pronouns: String?
16 let userType: String?
17 let receivesPaidServices: Bool?
18 let suspensionNotice: String?
10} 19}
Hutch/Views/Lookup/LookupView.swift +40 −7
@@ -1,4 +1,7 @@
1import SwiftUI 1import SwiftUI
2import os
3
4private let lookupLogger = Logger(subsystem: "net.cleberg.Hutch", category: "Lookup")
2 5
3enum LookupType: String, CaseIterable, Identifiable { 6enum LookupType: String, CaseIterable, Identifiable {
4 case user = "User" 7 case user = "User"
@@ -166,17 +169,47 @@ final class LookupViewModel {
166 let query = """ 169 let query = """
167 query userLookup($username: String!) { 170 query userLookup($username: String!) {
168 user: userByName(username: $username) { 171 user: userByName(username: $username) {
169 id username canonicalName email avatar 172 id
173 created
174 updated
175 canonicalName
176 username
177 email
178 url
179 location
180 bio
181 avatar
182 pronouns
183 userType
170 } 184 }
171 } 185 }
172 """ 186 """
173 187
174 let result = try await client.execute( 188 lookupLogger.debug("Looking up user profile for username: \(username, privacy: .public)")
175 service: .meta, 189
176 query: query, 190 let result: Response
177 variables: ["username": username], 191 do {
178 responseType: Response.self 192 result = try await client.execute(
179 ) 193 service: .meta,
194 query: query,
195 variables: ["username": username],
196 responseType: Response.self
197 )
198 } catch {
199 lookupLogger.error(
200 """
201 User lookup failed
202 username: \(username, privacy: .public)
203 query:
204 \(query, privacy: .public)
205 error:
206 \(String(describing: error), privacy: .public)
207 """
208 )
209 throw error
210 }
211
212 lookupLogger.debug("User lookup succeeded for username: \(username, privacy: .public)")
180 213
181 return .user(result.user) 214 return .user(result.user)
182 } 215 }
Hutch/Views/Lookup/UserProfileView.swift +51 −1
@@ -3,7 +3,12 @@ import SwiftUI
3struct UserProfileView: View { 3struct UserProfileView: View {
4 let user: User 4 let user: User
5 5
6 @Environment(AppState.self) private var appState 6 private static let iso8601Formatter: ISO8601DateFormatter = {
7 let formatter = ISO8601DateFormatter()
8 formatter.formatOptions = [.withInternetDateTime]
9 formatter.timeZone = TimeZone(secondsFromGMT: 0)
10 return formatter
11 }()
7 12
8 var body: some View { 13 var body: some View {
9 List { 14 List {
@@ -42,11 +47,56 @@ struct UserProfileView: View {
42 Section { 47 Section {
43 LabeledContent("Username", value: user.username) 48 LabeledContent("Username", value: user.username)
44 LabeledContent("Canonical Name", value: user.canonicalName) 49 LabeledContent("Canonical Name", value: user.canonicalName)
50 if let userType = user.userType {
51 LabeledContent("User Type", value: userType)
52 }
53 if let pronouns = user.pronouns {
54 LabeledContent("Pronouns", value: pronouns)
55 }
56 if let suspensionNotice = user.suspensionNotice {
57 LabeledContent("Suspension Notice", value: suspensionNotice)
58 }
59 }
60
61 Section {
45 LabeledContent("Email", value: user.email) 62 LabeledContent("Email", value: user.email)
63 if let urlString = user.url, let url = URL(string: urlString) {
64 LabeledContent("URL") {
65 Link(urlString, destination: url)
66 }
67 }
68 if let location = user.location {
69 LabeledContent("Location", value: location)
70 }
71 }
72
73 if let bio = user.bio {
74 Section("Bio") {
75 Text(bio)
76 }
77 }
78
79 if user.created != nil || user.updated != nil {
80 Section {
81 if let created = user.created {
82 LabeledContent("Joined", value: formattedTimestamp(created))
83 }
84 if let updated = user.updated {
85 LabeledContent("Updated", value: formattedTimestamp(updated))
86 }
87 }
46 } 88 }
47 } 89 }
48 .listStyle(.insetGrouped) 90 .listStyle(.insetGrouped)
49 .navigationTitle(user.canonicalName) 91 .navigationTitle(user.canonicalName)
50 .navigationBarTitleDisplayMode(.inline) 92 .navigationBarTitleDisplayMode(.inline)
51 } 93 }
94
95 private func formattedTimestamp(_ value: String) -> String {
96 guard let date = Self.iso8601Formatter.date(from: value) else {
97 return value
98 }
99
100 return date.formatted(date: .abbreviated, time: .shortened)
101 }
52} 102}