damus

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

CompatibleAttribute.swift (3685B)


      1 //
      2 //  CompatibleAttribute.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-04-06.
      6 //
      7 
      8 import Foundation
      9 import SwiftUI
     10 
     11 // Concatening too many `Text` objects can cause crashes (See https://github.com/damus-io/damus/issues/1826)
     12 fileprivate let MAX_TEXT_ITEMS = 100
     13 
     14 class CompatibleText: Equatable {
     15     var text: some View {
     16         if items.count > MAX_TEXT_ITEMS {
     17             return AnyView(
     18                 VStack {
     19                     Image("warning")
     20                     Text(NSLocalizedString("This note contains too many items and cannot be rendered", comment: "Error message indicating that a note is too big and cannot be rendered"))
     21                         .multilineTextAlignment(.center)
     22                 }
     23                 .foregroundColor(.secondary)
     24             )
     25         }
     26         return AnyView(
     27             items.reduce(Text(""), { (accumulated, item) in
     28                 return accumulated + item.render_to_text()
     29             })
     30         )
     31     }
     32     var attributed: AttributedString {
     33         return items.reduce(AttributedString(stringLiteral: ""), { (accumulated, item) in
     34             guard let item_attributed_string = item.attributed_string() else { return accumulated }
     35             return accumulated + item_attributed_string
     36         })
     37     }
     38     var items: [Item]
     39 
     40     init() {
     41         self.items = [.attributed_string(AttributedString(stringLiteral: ""))]
     42     }
     43 
     44     init(stringLiteral: String) {
     45         self.items = [.attributed_string(AttributedString(stringLiteral: stringLiteral))]
     46     }
     47 
     48     init(attributed: AttributedString) {
     49         self.items = [.attributed_string(attributed)]
     50     }
     51 
     52     init(items: [Item]) {
     53         self.items = items
     54     }
     55 
     56     static func == (lhs: CompatibleText, rhs: CompatibleText) -> Bool {
     57         return lhs.items == rhs.items
     58     }
     59 
     60     static func +(lhs: CompatibleText, rhs: CompatibleText) -> CompatibleText {
     61         if case .attributed_string(let las) = lhs.items.last,
     62            case .attributed_string(let ras) = rhs.items.first
     63         {
     64             // Concatenate attributed strings whenever possible to reduce item count
     65             let combined_attributed_string = las + ras
     66             return CompatibleText(items:
     67                 Array(lhs.items.prefix(upTo: lhs.items.count - 1)) +
     68                 [.attributed_string(combined_attributed_string)] +
     69                 Array(rhs.items.suffix(from: 1))
     70             )
     71         }
     72         else {
     73             return CompatibleText(items: lhs.items + rhs.items)
     74         }
     75     }
     76 
     77 }
     78 
     79 extension CompatibleText {
     80     enum Item: Equatable {
     81         case attributed_string(AttributedString)
     82         case icon(named: String, offset: CGFloat)
     83 
     84         func render_to_text() -> Text {
     85             switch self {
     86                 case .attributed_string(let attributed_string):
     87                     return Text(attributed_string)
     88                 case .icon(named: let image_name, offset: let offset):
     89                     return Text(Image(image_name)).baselineOffset(offset)
     90             }
     91         }
     92 
     93         func attributed_string() -> AttributedString? {
     94             switch self {
     95                 case .attributed_string(let attributed_string):
     96                     return attributed_string
     97                 case .icon(named: let name, offset: _):
     98                     guard let img = UIImage(named: name) else { return nil }
     99                     return icon_attributed_string(img: img)
    100             }
    101         }
    102     }
    103 }
    104 
    105 
    106 func icon_attributed_string(img: UIImage) -> AttributedString {
    107     let attachment = NSTextAttachment()
    108     attachment.image = img
    109     let attachmentString = NSAttributedString(attachment: attachment)
    110     return AttributedString(attachmentString)
    111 }
    112 
    113