iOS client for Grain grain.social
ios photography atproto
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: preserve follow state by passing viewer DID to getActorProfile

The server requires a viewer query parameter to return viewer relationship
state (following/followedBy). Also removes aggressive onAppear reload that
was resetting state, and wires up profile refresh after editing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+15 -15
+4 -2
Grain/API/Endpoints/ProfileEndpoints.swift
··· 65 65 // MARK: - Convenience Extensions 66 66 67 67 extension XRPCClient { 68 - func getActorProfile(actor: String, auth: AuthContext? = nil) async throws -> GrainProfileDetailed { 69 - try await query("social.grain.unspecced.getActorProfile", params: ["actor": actor], auth: auth, as: GrainProfileDetailed.self) 68 + func getActorProfile(actor: String, viewer: String? = nil, auth: AuthContext? = nil) async throws -> GrainProfileDetailed { 69 + var params = ["actor": actor] 70 + if let viewer { params["viewer"] = viewer } 71 + return try await query("social.grain.unspecced.getActorProfile", params: params, auth: auth, as: GrainProfileDetailed.self) 70 72 } 71 73 72 74 func getFollowers(actor: String, limit: Int = 50, cursor: String? = nil, auth: AuthContext? = nil) async throws -> GetFollowersResponse {
+2 -2
Grain/ViewModels/ProfileDetailViewModel.swift
··· 17 17 self.client = client 18 18 } 19 19 20 - func load(did: String, auth: AuthContext? = nil) async { 20 + func load(did: String, viewer: String? = nil, auth: AuthContext? = nil) async { 21 21 isLoading = true 22 22 error = nil 23 23 24 24 do { 25 - async let profileResponse = client.getActorProfile(actor: did, auth: auth) 25 + async let profileResponse = client.getActorProfile(actor: did, viewer: viewer, auth: auth) 26 26 async let feedResponse = client.getFeed(feed: "actor", actor: did, auth: auth) 27 27 async let storiesResponse = client.getStories(actor: did, auth: auth) 28 28
+5 -10
Grain/Views/Profile/ProfileView.swift
··· 7 7 @State private var selectedGalleryUri: String? 8 8 @State private var selectedProfileDid: String? 9 9 @State private var selectedHashtag: String? 10 - @State private var hasAppeared = false 11 10 let client: XRPCClient 12 11 let did: String 13 12 var isRoot = false ··· 126 125 if did == auth.userDID { 127 126 ToolbarItem(placement: .topBarTrailing) { 128 127 NavigationLink { 129 - SettingsView(client: client) 128 + SettingsView(client: client, onProfileEdited: { 129 + Task { await viewModel.load(did: did, viewer: auth.userDID, auth: auth.authContext()) } 130 + }) 130 131 } label: { 131 132 Image(systemName: "gearshape") 132 133 } ··· 144 145 } 145 146 .background(Color(.systemBackground)) 146 147 .refreshable { 147 - await viewModel.load(did: did, auth: auth.authContext()) 148 + await viewModel.load(did: did, viewer: auth.userDID, auth: auth.authContext()) 148 149 } 149 150 .task { 150 - await viewModel.load(did: did, auth: auth.authContext()) 151 - } 152 - .onAppear { 153 - if hasAppeared { 154 - Task { await viewModel.load(did: did, auth: auth.authContext()) } 155 - } 156 - hasAppeared = true 151 + await viewModel.load(did: did, viewer: auth.userDID, auth: auth.authContext()) 157 152 } 158 153 } 159 154 }
+2
Grain/Views/Settings/EditProfileView.swift
··· 6 6 @Environment(AuthManager.self) private var auth 7 7 @Environment(\.dismiss) private var dismiss 8 8 let client: XRPCClient 9 + var onSaved: (() -> Void)? 9 10 10 11 @State private var displayName = "" 11 12 @State private var bio = "" ··· 216 217 auth: authContext 217 218 ) 218 219 220 + onSaved?() 219 221 dismiss() 220 222 } catch { 221 223 errorMessage = "Failed to save: \(error.localizedDescription)"
+2 -1
Grain/Views/Settings/SettingsView.swift
··· 4 4 @Environment(AuthManager.self) private var auth 5 5 @Environment(\.dismiss) private var dismiss 6 6 let client: XRPCClient 7 + var onProfileEdited: (() -> Void)? 7 8 8 9 var body: some View { 9 10 List { ··· 19 20 20 21 Section { 21 22 NavigationLink("Edit Profile") { 22 - EditProfileView(client: client) 23 + EditProfileView(client: client, onSaved: onProfileEdited) 23 24 } 24 25 } 25 26