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.

feat: add atmosphere app logos to login marquee (#18)

Replace text-only marquee with actual app logo images from the
atmosphere ecosystem. Logos are rendered via Canvas for performance.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

authored by

Chad Miller
Claude Opus 4.6
and committed by
GitHub
fabd694e 3dd11b61

+405 -12
+13
Grain/Assets.xcassets/atmo-anisota.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-anisota.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-anisota.imageset/atmo-anisota.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:af9c33f46d901d6fc4d34c162249357c7f8599e0b7a3fdd3414b0030f0fc1885 3 + size 2415
+13
Grain/Assets.xcassets/atmo-beacon-bits.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-beacon-bits.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-beacon-bits.imageset/atmo-beacon-bits.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:d807b35edd339abe378bce4fcc622c98faac87b9f5fc74379522d525b47e3623 3 + size 2983
+13
Grain/Assets.xcassets/atmo-blacksky.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-blacksky.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-blacksky.imageset/atmo-blacksky.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:5eacb2ec3bfff92fec346c199088cbc0e70bd3c08b2296ac59acb06a54229e0d 3 + size 3213
+13
Grain/Assets.xcassets/atmo-blento.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-blento.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-blento.imageset/atmo-blento.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:4c913799aa195d272c91db5a31525acf742e6b4d7f301555baffa432ddf8446c 3 + size 2704
+13
Grain/Assets.xcassets/atmo-bluesky.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-bluesky.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-bluesky.imageset/atmo-bluesky.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:f50eb0bd2aaa825c5ecf7b224cb3fa86137c3e87912a739bca38c4c242fd7711 3 + size 2824
+13
Grain/Assets.xcassets/atmo-eurosky.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-eurosky.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-eurosky.imageset/atmo-eurosky.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:289b8573f95edc7aba66fd9be2af083c05867c9cc10434f9916420be0d38e504 3 + size 3688
+13
Grain/Assets.xcassets/atmo-flashes.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-flashes.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-flashes.imageset/atmo-flashes.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:370282b989dbde55709d162a23fc475ba59f2d4f293f046497ded2b0bee65cda 3 + size 2888
+13
Grain/Assets.xcassets/atmo-gander.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-gander.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-gander.imageset/atmo-gander.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:283ce540719c2463848d80fdd69fa257ffc08103db3bdbd14d5b543f4b986c9b 3 + size 2064
+13
Grain/Assets.xcassets/atmo-germ.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-germ.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-germ.imageset/atmo-germ.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:c7966469ce67ddc3a07fe3271752d99753a284c75af018d6353864220493838d 3 + size 3418
+13
Grain/Assets.xcassets/atmo-leaflet.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-leaflet.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-leaflet.imageset/atmo-leaflet.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:10d1e388adb61cb681ef1c7ec6fc7a0867642d30474a6e069ae4b2f3ae226b59 3 + size 2677
+13
Grain/Assets.xcassets/atmo-northsky.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-northsky.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-northsky.imageset/atmo-northsky.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:378ae03ff4ed603011672d15d66e205794c6bec32b2d4c905be3176636bb5c0e 3 + size 4113
+13
Grain/Assets.xcassets/atmo-offprint.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-offprint.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-offprint.imageset/atmo-offprint.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:b6f0f65b10ca7c288bcb307414b897679003dcb90a16476199010964a631cb8e 3 + size 3698
+13
Grain/Assets.xcassets/atmo-pckt.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-pckt.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-pckt.imageset/atmo-pckt.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:3a8493f8fa74a9134df429141c5e44ef7c408a3a6b8ca98f453d5ca917ddcfb8 3 + size 4745
+13
Grain/Assets.xcassets/atmo-plyr.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-plyr.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-plyr.imageset/atmo-plyr.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:a6f1d1189aa48cb64eabb9d25b70e649e532e1e7dd64643e2625d0b0564ee14b 3 + size 3002
+13
Grain/Assets.xcassets/atmo-popfeed.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-popfeed.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-popfeed.imageset/atmo-popfeed.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:2a161426023463ea0ff9d289f5340d6a06a94363bf1a106b197936dd25c0a1c2 3 + size 2413
+13
Grain/Assets.xcassets/atmo-semble.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-semble.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-semble.imageset/atmo-semble.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:1ae60b9ccbc9379fa331b354715665fc6ac2eabb781a5e0146a1f935cca01008 3 + size 2668
+13
Grain/Assets.xcassets/atmo-skylight.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-skylight.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-skylight.imageset/atmo-skylight.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:501fb12149cb7d8a4dd2ef2f7d9508a792aaada4b0e515fa58e1dbbb98a38be9 3 + size 2493
+13
Grain/Assets.xcassets/atmo-slices.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-slices.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-slices.imageset/atmo-slices.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:5285a931952ef509c35f9cf5691d969cb858de7b582565a2645238a2cee56fd6 3 + size 2405
+13
Grain/Assets.xcassets/atmo-spark.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-spark.png", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-spark.imageset/atmo-spark.png
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:a2927cc130541449032881753a9ff9e9c820920e8f3f8860a0159c3bd1c8fc92 3 + size 4469
+13
Grain/Assets.xcassets/atmo-stream-place.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-stream-place.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-stream-place.imageset/atmo-stream-place.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:ae0e80bd56d1436de4d45a077569a1505b21dde4fbe6f39c55687f39384a53c4 3 + size 2805
+13
Grain/Assets.xcassets/atmo-tangled.imageset/Contents.json
··· 1 + { 2 + "images": [ 3 + { 4 + "filename": "atmo-tangled.jpg", 5 + "idiom": "universal", 6 + "scale": "1x" 7 + } 8 + ], 9 + "info": { 10 + "author": "xcode", 11 + "version": 1 12 + } 13 + }
+3
Grain/Assets.xcassets/atmo-tangled.imageset/atmo-tangled.jpg
··· 1 + version https://git-lfs.github.com/spec/v1 2 + oid sha256:f08d8dc9459d3bf159605fd5abfe6a949e8fc09b830980164f562b4d6401b41e 3 + size 2950
+69 -12
Grain/Views/LoginView.swift
··· 73 73 74 74 if suggestions.isEmpty { 75 75 // Heading 76 - Text("Log in with your internet handle") 76 + Text("Log in with your atmosphere account") 77 77 .font(.title3.weight(.semibold)) 78 78 .foregroundStyle(.white) 79 79 .multilineTextAlignment(.center) 80 80 .padding(.bottom, 4) 81 81 82 - Text("Enter the domain you use as your identity across the open social web.") 83 - .font(.subheadline) 84 - .foregroundStyle(.white.opacity(0.7)) 85 - .multilineTextAlignment(.center) 86 - .padding(.horizontal, 8) 87 - 88 - Link("Learn more", destination: URL(string: "https://internethandle.org")!) 89 - .font(.subheadline.weight(.medium)) 90 - .underline() 91 - .foregroundStyle(.white) 82 + AtmosphereLogosMarquee() 92 83 .padding(.bottom, 16) 93 84 } 94 85 ··· 100 91 .font(.body.weight(.medium)) 101 92 .foregroundStyle(.white.opacity(0.5)) 102 93 103 - TextField("e.g. jasmine.garden", text: $handle, prompt: Text("e.g. jasmine.garden").foregroundStyle(.white.opacity(0.5))) 94 + TextField("e.g. user.bsky.social", text: $handle, prompt: Text("e.g. user.bsky.social").foregroundStyle(.white.opacity(0.5))) 104 95 .foregroundStyle(.white) 105 96 .textContentType(.username) 106 97 .autocorrectionDisabled() ··· 406 397 let avatar: String? 407 398 var id: String { 408 399 handle 400 + } 401 + } 402 + 403 + // MARK: - Atmosphere Logos Marquee 404 + 405 + private struct AtmosphereApp: Identifiable { 406 + let name: String 407 + let logo: String 408 + var id: String { 409 + name 410 + } 411 + } 412 + 413 + private struct AtmosphereLogosMarquee: View { 414 + private static let apps: [AtmosphereApp] = [ 415 + .init(name: "Bluesky", logo: "atmo-bluesky"), 416 + .init(name: "Tangled", logo: "atmo-tangled"), 417 + .init(name: "Anisota", logo: "atmo-anisota"), 418 + .init(name: "Beacon Bits", logo: "atmo-beacon-bits"), 419 + .init(name: "Eurosky", logo: "atmo-eurosky"), 420 + .init(name: "Flashes", logo: "atmo-flashes"), 421 + .init(name: "Gander", logo: "atmo-gander"), 422 + .init(name: "Germ", logo: "atmo-germ"), 423 + .init(name: "Leaflet", logo: "atmo-leaflet"), 424 + .init(name: "Northsky", logo: "atmo-northsky"), 425 + .init(name: "Offprint", logo: "atmo-offprint"), 426 + .init(name: "Pckt", logo: "atmo-pckt"), 427 + .init(name: "Plyr", logo: "atmo-plyr"), 428 + .init(name: "Popfeed", logo: "atmo-popfeed"), 429 + .init(name: "Blento", logo: "atmo-blento"), 430 + .init(name: "Semble", logo: "atmo-semble"), 431 + .init(name: "Skylight", logo: "atmo-skylight"), 432 + .init(name: "Blacksky", logo: "atmo-blacksky"), 433 + .init(name: "Spark", logo: "atmo-spark"), 434 + .init(name: "Stream Place", logo: "atmo-stream-place"), 435 + ] 436 + 437 + private let logoSize: CGFloat = 40 438 + private let itemSpacing: CGFloat = 12 439 + private let speed: CGFloat = 25 440 + 441 + private static let uiImages: [UIImage] = apps.compactMap { UIImage(named: $0.logo) } 442 + 443 + var body: some View { 444 + TimelineView(.animation) { (timeline: TimelineViewDefaultContext) in 445 + let now = timeline.date.timeIntervalSinceReferenceDate 446 + Canvas { ctx, size in 447 + let images = Self.uiImages 448 + let totalWidth = CGFloat(images.count) * (logoSize + itemSpacing) 449 + let scrollOffset = (CGFloat(now) * speed).truncatingRemainder(dividingBy: totalWidth) 450 + 451 + for i in 0 ..< images.count * 2 { 452 + let idx = i % images.count 453 + let x = CGFloat(i) * (logoSize + itemSpacing) - scrollOffset 454 + guard x + logoSize > 0, x < size.width else { continue } 455 + let rect = CGRect(x: x, y: (size.height - logoSize) / 2, width: logoSize, height: logoSize) 456 + ctx.drawLayer { layerCtx in 457 + let clipPath = RoundedRectangle(cornerRadius: 10).path(in: rect) 458 + layerCtx.clip(to: clipPath) 459 + layerCtx.draw(Image(uiImage: images[idx]), in: rect) 460 + } 461 + } 462 + } 463 + .frame(height: 44) 464 + } 465 + .clipped() 409 466 } 410 467 } 411 468