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 }