TimelineView.swift (4904B)
1 // 2 // TimelineView.swift 3 // damus 4 // 5 // Created by William Casarin on 2022-04-18. 6 // 7 8 import SwiftUI 9 10 struct TimelineView<Content: View>: View { 11 @ObservedObject var events: EventHolder 12 @Binding var loading: Bool 13 @Binding var headerHeight: CGFloat 14 @Binding var headerOffset: CGFloat 15 @State var shiftOffset: CGFloat = 0 16 @State var lastHeaderOffset: CGFloat = 0 17 @State var direction: SwipeDirection = .none 18 19 let damus: DamusState 20 let show_friend_icon: Bool 21 let filter: (NostrEvent) -> Bool 22 let content: Content? 23 let apply_mute_rules: Bool 24 25 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) { 26 self.events = events 27 self._loading = loading 28 self._headerHeight = headerHeight 29 self._headerOffset = headerOffset 30 self.damus = damus 31 self.show_friend_icon = show_friend_icon 32 self.filter = filter 33 self.apply_mute_rules = apply_mute_rules 34 self.content = content?() 35 } 36 37 init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) { 38 self.events = events 39 self._loading = loading 40 self._headerHeight = .constant(0.0) 41 self._headerOffset = .constant(0.0) 42 self.damus = damus 43 self.show_friend_icon = show_friend_icon 44 self.filter = filter 45 self.apply_mute_rules = apply_mute_rules 46 self.content = content?() 47 } 48 49 var body: some View { 50 MainContent 51 } 52 53 var MainContent: some View { 54 ScrollViewReader { scroller in 55 ScrollView { 56 if let content { 57 content 58 } 59 60 Color.clear 61 .id("startblock") 62 .frame(height: 0) 63 64 InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules) 65 .redacted(reason: loading ? .placeholder : []) 66 .shimmer(loading) 67 .disabled(loading) 68 .padding(.top, headerHeight - getSafeAreaTop()) 69 .offsetY { previous, current in 70 if previous > current{ 71 if direction != .up && current < 0 { 72 shiftOffset = current - headerOffset 73 direction = .up 74 lastHeaderOffset = headerOffset 75 } 76 77 let offset = current < 0 ? (current - shiftOffset) : 0 78 headerOffset = (-offset < headerHeight ? (offset < 0 ? offset : 0) : -headerHeight) 79 }else { 80 if direction != .down { 81 shiftOffset = current 82 direction = .down 83 lastHeaderOffset = headerOffset 84 } 85 86 let offset = lastHeaderOffset + (current - shiftOffset) 87 headerOffset = (offset > 0 ? 0 : offset) 88 } 89 } 90 .background { 91 GeometryReader { proxy -> Color in 92 handle_scroll_queue(proxy, queue: self.events) 93 return Color.clear 94 } 95 } 96 } 97 .coordinateSpace(name: "scroll") 98 .onReceive(handle_notify(.scroll_to_top)) { () in 99 events.flush() 100 self.events.should_queue = false 101 scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top) 102 } 103 } 104 .onAppear { 105 events.flush() 106 } 107 } 108 } 109 110 struct TimelineView_Previews: PreviewProvider { 111 @StateObject static var events = test_event_holder 112 static var previews: some View { 113 TimelineView<AnyView>(events: events, loading: .constant(true), damus: test_damus_state, show_friend_icon: true, filter: { _ in true }) 114 } 115 } 116 117 118 protocol ScrollQueue { 119 var should_queue: Bool { get } 120 func set_should_queue(_ val: Bool) 121 } 122 123 func handle_scroll_queue(_ proxy: GeometryProxy, queue: ScrollQueue) { 124 let offset = -proxy.frame(in: .named("scroll")).origin.y 125 guard offset >= 0 else { 126 return 127 } 128 let val = offset > 0 129 if queue.should_queue != val { 130 queue.set_should_queue(val) 131 } 132 }