FollowPackTimeline.swift (5042B)
1 // 2 // FollowPackTimeline.swift 3 // damus 4 // 5 // Created by eric on 5/6/25. 6 // 7 8 import SwiftUI 9 10 struct FollowPackTimelineView<Content: View>: View { 11 @ObservedObject var events: EventHolder 12 @Binding var loading: Bool 13 14 let damus: DamusState 15 let show_friend_icon: Bool 16 let filter: (NostrEvent) -> Bool 17 let content: Content? 18 let apply_mute_rules: Bool 19 20 init(events: EventHolder, loading: Binding<Bool>, headerHeight: Binding<CGFloat>, headerOffset: Binding<CGFloat>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) { 21 self.events = events 22 self._loading = loading 23 self.damus = damus 24 self.show_friend_icon = show_friend_icon 25 self.filter = filter 26 self.apply_mute_rules = apply_mute_rules 27 self.content = content?() 28 } 29 30 init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) { 31 self.events = events 32 self._loading = loading 33 self.damus = damus 34 self.show_friend_icon = show_friend_icon 35 self.filter = filter 36 self.apply_mute_rules = apply_mute_rules 37 self.content = content?() 38 } 39 40 var body: some View { 41 MainContent 42 } 43 44 var MainContent: some View { 45 ScrollViewReader { scroller in 46 ScrollView(.horizontal) { 47 if let content { 48 content 49 } 50 51 Color.clear 52 .id("startblock") 53 .frame(height: 0) 54 55 FollowPackInnerView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules) 56 .redacted(reason: loading ? .placeholder : []) 57 .shimmer(loading) 58 .disabled(loading) 59 .background { 60 GeometryReader { proxy -> Color in 61 handle_scroll_queue(proxy, queue: self.events) 62 return Color.clear 63 } 64 } 65 } 66 .coordinateSpace(name: "scroll") 67 .onReceive(handle_notify(.scroll_to_top)) { () in 68 events.flush() 69 self.events.should_queue = false 70 scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top) 71 } 72 } 73 .onAppear { 74 events.flush() 75 } 76 } 77 } 78 79 struct FollowPackInnerView: View { 80 @ObservedObject var events: EventHolder 81 let state: DamusState 82 let filter: (NostrEvent) -> Bool 83 84 init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) { 85 self.events = events 86 self.state = damus 87 self.filter = apply_mute_rules ? { filter($0) && !damus.mutelist_manager.is_event_muted($0) } : filter 88 } 89 90 var event_options: EventViewOptions { 91 if self.state.settings.truncate_timeline_text { 92 return [.wide, .truncate_content] 93 } 94 95 return [.wide] 96 } 97 98 var body: some View { 99 LazyHStack(spacing: 0) { 100 let events = self.events.events 101 if events.isEmpty { 102 EmptyTimelineView() 103 } else { 104 let evs = events.filter(filter) 105 let indexed = Array(zip(evs, 0...)) 106 ForEach(indexed, id: \.0.id) { tup in 107 let ev = tup.0 108 let ind = tup.1 109 let blur_imgs = should_blur_images(settings: state.settings, contacts: state.contacts, ev: ev, our_pubkey: state.pubkey) 110 if ev.kind == NostrKind.follow_list.rawValue { 111 FollowPackPreview(state: state, ev: ev, options: event_options, blur_imgs: blur_imgs) 112 .onTapGesture { 113 state.nav.push(route: Route.FollowPack(followPack: ev, model: FollowPackModel(damus_state: state), blur_imgs: blur_imgs)) 114 } 115 .padding(.top, 7) 116 .onAppear { 117 let to_preload = 118 Array([indexed[safe: ind+1]?.0, 119 indexed[safe: ind+2]?.0, 120 indexed[safe: ind+3]?.0, 121 indexed[safe: ind+4]?.0, 122 indexed[safe: ind+5]?.0 123 ].compactMap({ $0 })) 124 125 preload_events(state: state, events: to_preload) 126 } 127 } 128 } 129 } 130 } 131 .padding(.bottom) 132 133 } 134 } 135