LongformPreview.swift (7279B)
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, show_show_more_button: !options.contains(.no_show_more)) 55 .font(header ? .body : .caption) 56 .foregroundColor(.gray) 57 .padding(.horizontal, 10) 58 } 59 else if truncate { 60 TruncatedText(text: content, show_show_more_button: !options.contains(.no_show_more)) 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 .kfClickable() 102 .cornerRadius(1) 103 } 104 105 var body: some View { 106 Group { 107 if options.contains(.wide) { 108 Main.padding(.horizontal) 109 } else { 110 Main 111 } 112 } 113 } 114 115 var Main: some View { 116 VStack(alignment: .leading, spacing: 10) { 117 if let url = event.image { 118 if (self.options.contains(.no_media)) { 119 EmptyView() 120 } else if !blur_images || (!blur_images && !state.settings.media_previews) { 121 titleImage(url: url) 122 } else if blur_images || (blur_images && !state.settings.media_previews) { 123 ZStack { 124 titleImage(url: url) 125 BlurOverlayView(blur_images: $blur_images, artifacts: nil, size: nil, damus_state: nil, parentView: .longFormView) 126 } 127 } 128 } 129 130 Text(event.title ?? NSLocalizedString("Untitled", comment: "Title of longform event if it is untitled.")) 131 .font(header ? .title : .headline) 132 .padding(.horizontal, 10) 133 .padding(.top, 5) 134 135 if let summary = event.summary { 136 truncatedText(content: CompatibleText(stringLiteral: summary)) 137 } 138 139 if let labels = event.labels { 140 ScrollView(.horizontal) { 141 HStack { 142 ForEach(labels, id: \.self) { label in 143 Text(label) 144 .font(.caption) 145 .foregroundColor(.gray) 146 .padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15)) 147 .background(DamusColors.neutral1) 148 .cornerRadius(20) 149 .overlay( 150 RoundedRectangle(cornerRadius: 20) 151 .stroke(DamusColors.neutral3, lineWidth: 1) 152 ) 153 } 154 } 155 } 156 .scrollIndicators(.hidden) 157 .padding(10) 158 } 159 160 161 if case .loaded(let arts) = artifacts.state, 162 case .longform(let longform) = arts 163 { 164 Words(longform.words).font(.footnote) 165 .padding([.horizontal, .bottom], 10) 166 } 167 } 168 .frame(maxWidth: .infinity, alignment: .leading) 169 .background(DamusColors.neutral3) 170 .cornerRadius(10) 171 .overlay( 172 RoundedRectangle(cornerRadius: 10) 173 .stroke(DamusColors.neutral1, lineWidth: 1) 174 ) 175 .padding(.top, 10) 176 .onAppear { 177 blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event.event, our_pubkey: state.pubkey) 178 } 179 } 180 } 181 182 struct LongformPreview: View { 183 let state: DamusState 184 let event: LongformEvent 185 let options: EventViewOptions 186 187 init(state: DamusState, ev: NostrEvent, options: EventViewOptions) { 188 self.state = state 189 self.event = LongformEvent.parse(from: ev) 190 self.options = options.union(.no_mentions) 191 } 192 193 var body: some View { 194 EventShell(state: state, event: event.event, options: options) { 195 LongformPreviewBody(state: state, ev: event, options: options, header: false) 196 } 197 } 198 } 199 200 struct LongformPreview_Previews: PreviewProvider { 201 static var previews: some View { 202 VStack { 203 LongformPreview(state: test_damus_state, ev: test_longform_event.event, options: []) 204 205 LongformPreview(state: test_damus_state, ev: test_longform_event.event, options: [.wide]) 206 } 207 .frame(height: 400) 208 } 209 }