damus

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

LongformPreview.swift (7082B)


      1 //
      2 //  LongformPreview.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-06-01.
      6 //
      7 
      8 import SwiftUI
      9 import Kingfisher
     10 
     11 struct LongformPreviewBody: View {
     12     let state: DamusState
     13     let event: LongformEvent
     14     let options: EventViewOptions
     15     let header: Bool
     16     @State var blur_images: Bool = true
     17     
     18     @ObservedObject var artifacts: NoteArtifactsModel
     19 
     20     init(state: DamusState, ev: LongformEvent, options: EventViewOptions, header: Bool) {
     21         self.state = state
     22         self.event = ev
     23         self.options = options
     24         self.header = header
     25 
     26         self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
     27     }
     28 
     29     init(state: DamusState, ev: NostrEvent, options: EventViewOptions, header: Bool) {
     30         self.state = state
     31         self.event = LongformEvent.parse(from: ev)
     32         self.options = options
     33         self.header = header
     34 
     35         self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
     36     }
     37 
     38     func Words(_ words: Int) -> Text {
     39         let wordCount = pluralizedString(key: "word_count", count: words)
     40         return Text(wordCount)
     41     }
     42     
     43     var truncate: Bool {
     44         return options.contains(.truncate_content)
     45     }
     46     
     47     var truncate_very_short: Bool {
     48         return options.contains(.truncate_content_very_short)
     49     }
     50     
     51     func truncatedText(content: CompatibleText) -> some View {
     52         Group {
     53             if truncate_very_short {
     54                 TruncatedText(text: content, maxChars: 140)
     55                     .font(header ? .body : .caption)
     56                     .foregroundColor(.gray)
     57                     .padding(.horizontal, 10)
     58             }
     59             else if truncate {
     60                 TruncatedText(text: content)
     61                     .font(header ? .body : .caption)
     62                     .foregroundColor(.gray)
     63                     .padding(.horizontal, 10)
     64             } else {
     65                 content.text
     66                     .font(header ? .body : .caption)
     67                     .foregroundColor(.gray)
     68                     .padding(.horizontal, 10)
     69             }
     70         }
     71     }
     72     
     73     func Placeholder(url: URL) -> some View {
     74         Group {
     75             if let meta = state.events.lookup_img_metadata(url: url),
     76                case .processed(let blurhash) = meta.state {
     77                 Image(uiImage: blurhash)
     78                     .resizable()
     79                     .frame(maxWidth: .infinity, maxHeight: header ? .infinity : 150)
     80             } else {
     81                 DamusColors.adaptableWhite
     82             }
     83         }
     84     }
     85     
     86     func titleImage(url: URL) -> some View {
     87         KFAnimatedImage(url)
     88             .callbackQueue(.dispatch(.global(qos:.background)))
     89             .backgroundDecode(true)
     90             .imageContext(.note, disable_animation: state.settings.disable_animation)
     91             .image_fade(duration: 0.25)
     92             .cancelOnDisappear(true)
     93             .configure { view in
     94                 view.framePreloadCount = 3
     95             }
     96             .background {
     97                 Placeholder(url: url)
     98             }
     99             .aspectRatio(contentMode: .fill)
    100             .frame(maxWidth: .infinity, maxHeight: header ? .infinity : 150)
    101             .cornerRadius(1)
    102     }
    103 
    104     var body: some View {
    105         Group {
    106             if options.contains(.wide) {
    107                 Main.padding(.horizontal)
    108             } else {
    109                 Main
    110             }
    111         }
    112     }
    113 
    114     var Main: some View {
    115         VStack(alignment: .leading, spacing: 10) {
    116             if let url = event.image {
    117                 if (self.options.contains(.no_media)) {
    118                     EmptyView()
    119                 } else if !blur_images || (!blur_images && !state.settings.media_previews) {
    120                     titleImage(url: url)
    121                 } else if blur_images || (blur_images && !state.settings.media_previews) {
    122                     ZStack {
    123                         titleImage(url: url)
    124                         Blur()
    125                             .onTapGesture {
    126                                 blur_images = false
    127                             }
    128                     }
    129                 }
    130             }
    131             
    132             Text(event.title ?? "Untitled")
    133                 .font(header ? .title : .headline)
    134                 .padding(.horizontal, 10)
    135                 .padding(.top, 5)
    136             
    137             if let summary = event.summary {
    138                 truncatedText(content: CompatibleText(stringLiteral: summary))
    139             }
    140             
    141             if let labels = event.labels {
    142                 ScrollView(.horizontal) {
    143                     HStack {
    144                         ForEach(labels, id: \.self) { label in
    145                             Text(label)
    146                                 .font(.caption)
    147                                 .foregroundColor(.gray)
    148                                 .padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
    149                                 .background(DamusColors.neutral1)
    150                                 .cornerRadius(20)
    151                                 .overlay(
    152                                     RoundedRectangle(cornerRadius: 20)
    153                                         .stroke(DamusColors.neutral3, lineWidth: 1)
    154                                 )
    155                         }
    156                     }
    157                 }
    158                 .scrollIndicators(.hidden)
    159                 .padding(10)
    160             }
    161 
    162             
    163             if case .loaded(let arts) = artifacts.state,
    164                case .longform(let longform) = arts
    165             {
    166                 Words(longform.words).font(.footnote)
    167                     .padding([.horizontal, .bottom], 10)
    168             }
    169         }
    170         .frame(maxWidth: .infinity, alignment: .leading)
    171         .background(DamusColors.neutral3)
    172         .cornerRadius(10)
    173         .overlay(
    174             RoundedRectangle(cornerRadius: 10)
    175                 .stroke(DamusColors.neutral1, lineWidth: 1)
    176         )
    177         .padding(.top, 10)
    178         .onAppear {
    179             blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event.event, our_pubkey: state.pubkey)
    180         }
    181     }
    182 }
    183 
    184 struct LongformPreview: View {
    185     let state: DamusState
    186     let event: LongformEvent
    187     let options: EventViewOptions
    188 
    189     init(state: DamusState, ev: NostrEvent, options: EventViewOptions) {
    190         self.state = state
    191         self.event = LongformEvent.parse(from: ev)
    192         self.options = options.union(.no_mentions)
    193     }
    194 
    195     var body: some View {
    196         EventShell(state: state, event: event.event, options: options) {
    197             LongformPreviewBody(state: state, ev: event, options: options, header: false)
    198         }
    199     }
    200 }
    201 
    202 struct LongformPreview_Previews: PreviewProvider {
    203     static var previews: some View {
    204         VStack {
    205             LongformPreview(state: test_damus_state, ev: test_longform_event.event, options: [])
    206 
    207             LongformPreview(state: test_damus_state, ev: test_longform_event.event, options: [.wide])
    208         }
    209         .frame(height: 400)
    210     }
    211 }