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.

chore: enrich preview data for stories and comments

Adds mock stories (with real bundle images for Kyoto/Oregon/Portland),
story-specific comment fixtures (including a threaded reply), and
avatars on the secondary preview profiles so they render with content
instead of gray circles.

Also wires the StoryViewer preview to inject initialStories so it
displays something on first load, and adds a populated StoryCommentSheet
preview showing the sheet at medium detent with mock comments visible.

+149 -13
+119 -7
Grain/Utilities/PreviewData.swift
··· 21 21 22 22 static let profile2 = GrainProfile( 23 23 cid: "cid2", did: "did:plc:prevuser2", 24 - handle: "marcus.grain.social", displayName: "Marcus Webb" 24 + handle: "marcus.grain.social", displayName: "Marcus Webb", 25 + avatar: bundleImageURL("Union_Bank_Tower,_Portland_(2024)-L1006272") 25 26 ) 26 27 27 28 static let profile3 = GrainProfile( 28 29 cid: "cid3", did: "did:plc:prevuser3", 29 - handle: "sofia.grain.social", displayName: "Sofia Reyes" 30 + handle: "sofia.grain.social", displayName: "Sofia Reyes", 31 + avatar: bundleImageURL("Portland_Japanese_Garden_maple") 32 + ) 33 + 34 + static let profile4 = GrainProfile( 35 + cid: "cid4", did: "did:plc:prevuser4", 36 + handle: "kai.grain.social", displayName: "Kai Müller", 37 + avatar: bundleImageURL("Mount_Hood_reflected_in_Mirror_Lake,_Oregon") 38 + ) 39 + 40 + static let profile5 = GrainProfile( 41 + cid: "cid5", did: "did:plc:prevuser5", 42 + handle: "leo.grain.social", displayName: "Leo Park", 43 + avatar: bundleImageURL("Mt_Herschel,_Antarctica,_Jan_2006") 30 44 ) 31 45 32 46 // MARK: - Bundle image URL helper ··· 183 197 ), 184 198 ] 185 199 200 + // MARK: - Stories 201 + 202 + private static let yukiProfile = GrainProfile( 203 + cid: "c1", 204 + did: "did:plc:prevuser1", 205 + handle: "yuki.grain.social", 206 + displayName: "Yuki Tanaka", 207 + avatar: bundleImageURL("Penguin_in_Antarctica_jumping_out_of_the_water") 208 + ) 209 + 210 + static let stories: [GrainStory] = [ 211 + GrainStory( 212 + uri: "at://did:plc:prevuser1/social.grain.story/s1", 213 + cid: "cid", 214 + creator: yukiProfile, 215 + thumb: bundleImageURL("Portland_Japanese_Garden_maple"), 216 + fullsize: bundleImageURL("Portland_Japanese_Garden_maple"), 217 + aspectRatio: AspectRatio(width: 4, height: 3), 218 + location: H3Location(value: "8a2a1072b59ffff", name: "Kyoto, Japan"), 219 + address: nil, 220 + createdAt: "2025-01-10T18:00:00Z", 221 + labels: nil, 222 + crossPost: nil, 223 + viewer: nil 224 + ), 225 + GrainStory( 226 + uri: "at://did:plc:prevuser1/social.grain.story/s2", 227 + cid: "cid", 228 + creator: yukiProfile, 229 + thumb: bundleImageURL("Mount_Hood_reflected_in_Mirror_Lake,_Oregon"), 230 + fullsize: bundleImageURL("Mount_Hood_reflected_in_Mirror_Lake,_Oregon"), 231 + aspectRatio: AspectRatio(width: 3, height: 2), 232 + location: H3Location(value: "8a2a1072b51ffff", name: "Mirror Lake, Oregon"), 233 + address: nil, 234 + createdAt: "2025-01-10T16:00:00Z", 235 + labels: nil, 236 + crossPost: nil, 237 + viewer: nil 238 + ), 239 + GrainStory( 240 + uri: "at://did:plc:prevuser1/social.grain.story/s3", 241 + cid: "cid", 242 + creator: yukiProfile, 243 + thumb: bundleImageURL("Union_Bank_Tower,_Portland_(2024)-L1006272"), 244 + fullsize: bundleImageURL("Union_Bank_Tower,_Portland_(2024)-L1006272"), 245 + aspectRatio: AspectRatio(width: 2, height: 3), 246 + location: H3Location(value: "8a2a1072b52ffff", name: "Portland, OR"), 247 + address: nil, 248 + createdAt: "2025-01-10T14:00:00Z", 249 + labels: nil, 250 + crossPost: nil, 251 + viewer: nil 252 + ), 253 + ] 254 + 255 + static let storyComments: [GrainComment] = [ 256 + GrainComment( 257 + uri: "at://did:plc:prevuser2/social.grain.comment/sc1", 258 + cid: "cid", 259 + author: profile2, 260 + text: "The sunlight through those maple leaves 😍", 261 + subject: AnyCodable(stories[0].uri), 262 + createdAt: "2025-01-10T18:30:00Z" 263 + ), 264 + GrainComment( 265 + uri: "at://did:plc:prevuser3/social.grain.comment/sc2", 266 + cid: "cid", 267 + author: profile3, 268 + text: "Kyoto in fall hits different. Which temple is this near?", 269 + subject: AnyCodable(stories[0].uri), 270 + createdAt: "2025-01-10T18:42:00Z" 271 + ), 272 + GrainComment( 273 + uri: "at://did:plc:prevuser1/social.grain.comment/sc3", 274 + cid: "cid", 275 + author: yukiProfile, 276 + text: "@sofia.grain.social it's Tofuku-ji — the whole valley is lit up right now", 277 + replyTo: "at://did:plc:prevuser3/social.grain.comment/sc2", 278 + createdAt: "2025-01-10T18:50:00Z" 279 + ), 280 + GrainComment( 281 + uri: "at://did:plc:prevuser4/social.grain.comment/sc4", 282 + cid: "cid", 283 + author: profile4, 284 + text: "Portra 400 pushing 1 stop?", 285 + subject: AnyCodable(stories[0].uri), 286 + createdAt: "2025-01-10T19:05:00Z" 287 + ), 288 + GrainComment( 289 + uri: "at://did:plc:prevuser5/social.grain.comment/sc5", 290 + cid: "cid", 291 + author: profile5, 292 + text: "Adding this to my travel bucket list 🍁", 293 + subject: AnyCodable(stories[0].uri), 294 + createdAt: "2025-01-10T19:12:00Z" 295 + ), 296 + ] 297 + 186 298 // MARK: - PhotoItems (UIImage-based, for editor/grid/strip previews) 187 299 188 300 static var photoItems: [PhotoItem] { ··· 246 358 // MARK: - Story authors 247 359 248 360 static let storyAuthors: [GrainStoryAuthor] = [ 249 - GrainStoryAuthor(profile: GrainProfile(cid: "c1", did: "did:plc:prevuser1", handle: "yuki.grain.social", displayName: "Yuki"), storyCount: 3, latestAt: "2025-01-10T18:00:00Z"), 250 - GrainStoryAuthor(profile: GrainProfile(cid: "c2", did: "did:plc:prevuser2", handle: "marcus.grain.social", displayName: "Marcus"), storyCount: 1, latestAt: "2025-01-10T15:00:00Z"), 251 - GrainStoryAuthor(profile: GrainProfile(cid: "c3", did: "did:plc:prevuser3", handle: "sofia.grain.social", displayName: "Sofia"), storyCount: 2, latestAt: "2025-01-10T12:00:00Z"), 252 - GrainStoryAuthor(profile: GrainProfile(cid: "c4", did: "did:plc:prevuser4", handle: "kai.grain.social", displayName: "Kai"), storyCount: 1, latestAt: "2025-01-10T10:00:00Z"), 253 - GrainStoryAuthor(profile: GrainProfile(cid: "c5", did: "did:plc:prevuser5", handle: "leo.grain.social", displayName: "Leo"), storyCount: 4, latestAt: "2025-01-10T08:00:00Z"), 361 + GrainStoryAuthor(profile: yukiProfile, storyCount: 3, latestAt: "2025-01-10T18:00:00Z"), 362 + GrainStoryAuthor(profile: profile2, storyCount: 1, latestAt: "2025-01-10T15:00:00Z"), 363 + GrainStoryAuthor(profile: profile3, storyCount: 2, latestAt: "2025-01-10T12:00:00Z"), 364 + GrainStoryAuthor(profile: profile4, storyCount: 1, latestAt: "2025-01-10T10:00:00Z"), 365 + GrainStoryAuthor(profile: profile5, storyCount: 4, latestAt: "2025-01-10T08:00:00Z"), 254 366 ] 255 367 256 368 // MARK: - Notifications
+19
Grain/Views/Stories/StoryCommentSheet.swift
··· 164 164 await viewModel.deleteComment(comment, storyUri: storyUri, auth: authContext) 165 165 } 166 166 } 167 + 168 + #Preview { 169 + let vm = StoryCommentsViewModel(client: XRPCClient(baseURL: AuthManager.serverURL)) 170 + vm.comments = PreviewData.storyComments 171 + vm.totalCount = PreviewData.storyComments.count 172 + 173 + return Color.black 174 + .ignoresSafeArea() 175 + .sheet(isPresented: .constant(true)) { 176 + StoryCommentSheet( 177 + viewModel: vm, 178 + storyUri: PreviewData.stories[0].uri, 179 + client: XRPCClient(baseURL: AuthManager.serverURL) 180 + ) 181 + .environment(AuthManager()) 182 + .environment(StoryStatusCache()) 183 + .environment(ViewedStoryStorage()) 184 + } 185 + }
+11 -6
Grain/Views/Stories/StoryViewer.swift
··· 1144 1144 } 1145 1145 1146 1146 #Preview { 1147 - StoryViewer(authors: PreviewData.storyAuthors, client: XRPCClient(baseURL: AuthManager.serverURL)) 1148 - .environment(AuthManager()) 1149 - .environment(LabelDefinitionsCache()) 1150 - .environment(ViewedStoryStorage()) 1151 - .environment(StoryStatusCache()) 1152 - .environment(StoryFavoriteCache()) 1147 + StoryViewer( 1148 + authors: PreviewData.storyAuthors, 1149 + startAuthorDid: "did:plc:prevuser1", 1150 + initialStories: PreviewData.stories, 1151 + client: XRPCClient(baseURL: AuthManager.serverURL) 1152 + ) 1153 + .environment(AuthManager()) 1154 + .environment(LabelDefinitionsCache()) 1155 + .environment(ViewedStoryStorage()) 1156 + .environment(StoryStatusCache()) 1157 + .environment(StoryFavoriteCache()) 1153 1158 }