LongformPreview.swift (7297B)
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 Blur() 126 .onTapGesture { 127 blur_images = false 128 } 129 } 130 } 131 } 132 133 Text(event.title ?? NSLocalizedString("Untitled", comment: "Title of longform event if it is untitled.")) 134 .font(header ? .title : .headline) 135 .padding(.horizontal, 10) 136 .padding(.top, 5) 137 138 if let summary = event.summary { 139 truncatedText(content: CompatibleText(stringLiteral: summary)) 140 } 141 142 if let labels = event.labels { 143 ScrollView(.horizontal) { 144 HStack { 145 ForEach(labels, id: \.self) { label in 146 Text(label) 147 .font(.caption) 148 .foregroundColor(.gray) 149 .padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15)) 150 .background(DamusColors.neutral1) 151 .cornerRadius(20) 152 .overlay( 153 RoundedRectangle(cornerRadius: 20) 154 .stroke(DamusColors.neutral3, lineWidth: 1) 155 ) 156 } 157 } 158 } 159 .scrollIndicators(.hidden) 160 .padding(10) 161 } 162 163 164 if case .loaded(let arts) = artifacts.state, 165 case .longform(let longform) = arts 166 { 167 Words(longform.words).font(.footnote) 168 .padding([.horizontal, .bottom], 10) 169 } 170 } 171 .frame(maxWidth: .infinity, alignment: .leading) 172 .background(DamusColors.neutral3) 173 .cornerRadius(10) 174 .overlay( 175 RoundedRectangle(cornerRadius: 10) 176 .stroke(DamusColors.neutral1, lineWidth: 1) 177 ) 178 .padding(.top, 10) 179 .onAppear { 180 blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event.event, our_pubkey: state.pubkey) 181 } 182 } 183 } 184 185 struct LongformPreview: View { 186 let state: DamusState 187 let event: LongformEvent 188 let options: EventViewOptions 189 190 init(state: DamusState, ev: NostrEvent, options: EventViewOptions) { 191 self.state = state 192 self.event = LongformEvent.parse(from: ev) 193 self.options = options.union(.no_mentions) 194 } 195 196 var body: some View { 197 EventShell(state: state, event: event.event, options: options) { 198 LongformPreviewBody(state: state, ev: event, options: options, header: false) 199 } 200 } 201 } 202 203 struct LongformPreview_Previews: PreviewProvider { 204 static var previews: some View { 205 VStack { 206 LongformPreview(state: test_damus_state, ev: test_longform_event.event, options: []) 207 208 LongformPreview(state: test_damus_state, ev: test_longform_event.event, options: [.wide]) 209 } 210 .frame(height: 400) 211 } 212 }