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: notification navigation to gallery detail and profile

Tap notification row to navigate to gallery detail (or profile for follows).
Tap avatar/username to navigate to that user's profile.

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

+31 -1
+31 -1
Grain/Views/Notifications/NotificationsView.swift
··· 4 4 struct NotificationsView: View { 5 5 @Environment(AuthManager.self) private var auth 6 6 @State private var viewModel: NotificationsViewModel 7 + @State private var selectedGalleryUri: String? 8 + @State private var selectedProfileDid: String? 9 + let client: XRPCClient 7 10 8 11 init(client: XRPCClient) { 12 + self.client = client 9 13 _viewModel = State(initialValue: NotificationsViewModel(client: client)) 10 14 } 11 15 ··· 13 17 NavigationStack { 14 18 List { 15 19 ForEach(viewModel.notifications) { notification in 16 - NotificationRow(notification: notification) 20 + NotificationRow(notification: notification, onProfileTap: { did in 21 + selectedProfileDid = did 22 + }) 23 + .contentShape(Rectangle()) 24 + .onTapGesture { 25 + if notification.reasonType == .follow { 26 + selectedProfileDid = notification.author.did 27 + } else if let galleryUri = notification.galleryUri { 28 + selectedGalleryUri = galleryUri 29 + } 30 + } 31 + .swipeActions(edge: .leading) { 32 + Button { 33 + selectedProfileDid = notification.author.did 34 + } label: { 35 + Label("Profile", systemImage: "person") 36 + } 37 + } 17 38 .onAppear { 18 39 if notification.id == viewModel.notifications.last?.id { 19 40 Task { await viewModel.loadMore(auth: auth.authContext()) } ··· 34 55 await viewModel.loadInitial(auth: auth.authContext()) 35 56 } 36 57 .navigationTitle("Notifications") 58 + .navigationDestination(item: $selectedGalleryUri) { uri in 59 + GalleryDetailView(client: client, galleryUri: uri) 60 + } 61 + .navigationDestination(item: $selectedProfileDid) { did in 62 + ProfileView(client: client, did: did) 63 + } 37 64 .task { 38 65 if viewModel.notifications.isEmpty { 39 66 await viewModel.loadInitial(auth: auth.authContext()) ··· 45 72 46 73 struct NotificationRow: View { 47 74 let notification: GrainNotification 75 + var onProfileTap: ((String) -> Void)? 48 76 49 77 var body: some View { 50 78 HStack(alignment: .top, spacing: 12) { 51 79 AvatarView(url: notification.author.avatar, size: 36) 80 + .onTapGesture { onProfileTap?(notification.author.did) } 52 81 53 82 VStack(alignment: .leading, spacing: 4) { 54 83 HStack(spacing: 4) { 55 84 Text(notification.author.displayName ?? notification.author.handle) 56 85 .font(.subheadline.bold()) 86 + .onTapGesture { onProfileTap?(notification.author.did) } 57 87 Text(reasonText) 58 88 .font(.subheadline) 59 89 .foregroundStyle(.secondary)