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: center and scale notification reason icons, add multi-user preview data

+89 -9
+85 -6
Grain/Utilities/PreviewData.swift
··· 51 51 avatar: bundleImageURL("Mt_Herschel,_Antarctica,_Jan_2006") 52 52 ) 53 53 54 + static let profile6 = GrainProfile( 55 + cid: "cid6", did: "did:plc:prevuser6", 56 + handle: "rina.grain.social", displayName: "Rina Watanabe", 57 + avatar: bundleImageURL("ACE_EMD_F40PH_Fremont_-_San_Jose") 58 + ) 59 + 60 + static let profile7 = GrainProfile( 61 + cid: "cid7", did: "did:plc:prevuser7", 62 + handle: "omar.grain.social", displayName: "Omar Hassan", 63 + avatar: bundleImageURL("C-141_Starlifter_contrail") 64 + ) 65 + 66 + static let profile8 = GrainProfile( 67 + cid: "cid8", did: "did:plc:prevuser8", 68 + handle: "elena.grain.social", displayName: "Elena Voronova", 69 + avatar: bundleImageURL("Endeavour_after_STS-126_on_SCA_over_Mojave_from_above") 70 + ) 71 + 54 72 // MARK: - Bundle image URL helper 55 73 56 74 static func bundleImageURL(_ name: String, ext: String = "jpg") -> String { ··· 367 385 // MARK: - Notifications 368 386 369 387 static let notifications: [GrainNotification] = [ 388 + // — Gallery favorite group: 4 users liked gallery1 within 48h → "Marcus and 3 others favorited your gallery" 370 389 GrainNotification( 371 390 uri: "at://did:plc:prevuser2/social.grain.notification/n1", 372 391 reason: "gallery-favorite", ··· 374 393 author: profile2, 375 394 galleryUri: gallery1.uri, 376 395 galleryTitle: gallery1.title, 377 - galleryThumb: "" 396 + galleryThumb: bundleImageURL("Portland_Japanese_Garden_maple") 397 + ), 398 + GrainNotification( 399 + uri: "at://did:plc:prevuser6/social.grain.notification/n1b", 400 + reason: "gallery-favorite", 401 + createdAt: "2025-01-10T18:45:00Z", 402 + author: profile6, 403 + galleryUri: gallery1.uri, 404 + galleryTitle: gallery1.title, 405 + galleryThumb: bundleImageURL("Portland_Japanese_Garden_maple") 406 + ), 407 + GrainNotification( 408 + uri: "at://did:plc:prevuser7/social.grain.notification/n1c", 409 + reason: "gallery-favorite", 410 + createdAt: "2025-01-10T17:20:00Z", 411 + author: profile7, 412 + galleryUri: gallery1.uri, 413 + galleryTitle: gallery1.title, 414 + galleryThumb: bundleImageURL("Portland_Japanese_Garden_maple") 378 415 ), 379 416 GrainNotification( 417 + uri: "at://did:plc:prevuser8/social.grain.notification/n1d", 418 + reason: "gallery-favorite", 419 + createdAt: "2025-01-10T16:00:00Z", 420 + author: profile8, 421 + galleryUri: gallery1.uri, 422 + galleryTitle: gallery1.title, 423 + galleryThumb: bundleImageURL("Portland_Japanese_Garden_maple") 424 + ), 425 + // — Single gallery comment 426 + GrainNotification( 380 427 uri: "at://did:plc:prevuser3/social.grain.notification/n2", 381 428 reason: "gallery-comment", 382 429 createdAt: "2025-01-10T19:00:00Z", 383 430 author: profile3, 384 431 galleryUri: gallery1.uri, 385 432 galleryTitle: gallery1.title, 386 - galleryThumb: "", 433 + galleryThumb: bundleImageURL("Portland_Japanese_Garden_maple"), 387 434 commentText: "The light in the third frame is unreal. What film stock?" 388 435 ), 436 + // — Follow group: 3 users followed within 48h → "Kai and 2 others followed you" 389 437 GrainNotification( 390 438 uri: "at://did:plc:prevuser4/social.grain.notification/n3", 391 439 reason: "follow", 392 440 createdAt: "2025-01-10T18:00:00Z", 393 - author: GrainProfile(cid: "c4", did: "did:plc:prevuser4", handle: "kai.grain.social", displayName: "Kai Müller") 441 + author: profile4 442 + ), 443 + GrainNotification( 444 + uri: "at://did:plc:prevuser6/social.grain.notification/n3b", 445 + reason: "follow", 446 + createdAt: "2025-01-10T15:00:00Z", 447 + author: profile6 394 448 ), 395 449 GrainNotification( 450 + uri: "at://did:plc:prevuser7/social.grain.notification/n3c", 451 + reason: "follow", 452 + createdAt: "2025-01-10T14:30:00Z", 453 + author: profile7 454 + ), 455 + // — Story favorite group: 2 users liked the same story → "Sofia and 1 other favorited your story" 456 + GrainNotification( 457 + uri: "at://did:plc:prevuser3/social.grain.notification/n6", 458 + reason: "story-favorite", 459 + createdAt: "2025-01-10T13:00:00Z", 460 + author: profile3, 461 + storyUri: stories[0].uri, 462 + storyThumb: bundleImageURL("Portland_Japanese_Garden_maple") 463 + ), 464 + GrainNotification( 465 + uri: "at://did:plc:prevuser5/social.grain.notification/n6b", 466 + reason: "story-favorite", 467 + createdAt: "2025-01-10T12:30:00Z", 468 + author: profile5, 469 + storyUri: stories[0].uri, 470 + storyThumb: bundleImageURL("Portland_Japanese_Garden_maple") 471 + ), 472 + // — Single gallery favorite (different gallery, no group) 473 + GrainNotification( 396 474 uri: "at://did:plc:prevuser2/social.grain.notification/n4", 397 475 reason: "gallery-favorite", 398 476 createdAt: "2025-01-09T12:00:00Z", 399 477 author: profile2, 400 478 galleryUri: gallery2.uri, 401 479 galleryTitle: gallery2.title, 402 - galleryThumb: "" 480 + galleryThumb: bundleImageURL("Mount_Hood_reflected_in_Mirror_Lake,_Oregon") 403 481 ), 482 + // — Single comment mention 404 483 GrainNotification( 405 484 uri: "at://did:plc:prevuser5/social.grain.notification/n5", 406 485 reason: "gallery-comment-mention", 407 486 createdAt: "2025-01-09T10:00:00Z", 408 - author: GrainProfile(cid: "c5", did: "did:plc:prevuser5", handle: "leo.grain.social", displayName: "Leo Park"), 487 + author: profile5, 409 488 galleryUri: gallery1.uri, 410 489 galleryTitle: gallery1.title, 411 - galleryThumb: "", 490 + galleryThumb: bundleImageURL("Portland_Japanese_Garden_maple"), 412 491 commentText: "Tagged you in a comment: @yuki.grain.social beautiful work!" 413 492 ), 414 493 ]
+4 -3
Grain/Views/Notifications/NotificationsView.swift
··· 203 203 var body: some View { 204 204 Image(systemName: iconName) 205 205 .foregroundStyle(Color("AccentColor")) 206 - .font(.system(size: 14)) 206 + .font(.system(size: 18)) 207 207 .frame(width: 20) 208 208 } 209 209 } ··· 223 223 var body: some View { 224 224 HStack(alignment: .top, spacing: 10) { 225 225 ReasonIcon(reason: group.notification.reasonType) 226 - .padding(.top, 4) 226 + .frame(height: 38) 227 227 228 228 VStack(alignment: .leading, spacing: 6) { 229 229 HStack(spacing: 0) { ··· 310 310 var body: some View { 311 311 HStack(alignment: .top, spacing: 10) { 312 312 ReasonIcon(reason: notification.reasonType) 313 - .padding(.top, 4) 313 + .frame(height: 34) 314 314 315 315 VStack(alignment: .leading, spacing: 6) { 316 316 AvatarView(url: notification.author.avatar, size: 34, animated: false) ··· 605 605 vm.unseenCount = 3 606 606 return NotificationsView(client: client, viewModel: vm) 607 607 .previewEnvironments() 608 + .grainPreview() 608 609 .frame(maxHeight: .infinity, alignment: .top) 609 610 }