damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }