Files
damus/damus/Util/CompatibleAttribute.swift
T
2024-04-25 09:30:27 -04:00

114 lines
3.6 KiB
Swift

//
// CompatibleAttribute.swift
// damus
//
// Created by William Casarin on 2023-04-06.
//
import Foundation
import SwiftUI
// Concatening too many `Text` objects can cause crashes (See https://github.com/damus-io/damus/issues/1826)
fileprivate let MAX_TEXT_ITEMS = 100
class CompatibleText: Equatable {
var text: some View {
if items.count > MAX_TEXT_ITEMS {
return AnyView(
VStack {
Image("warning")
Text("This note contains too many items and cannot be rendered", comment: "Error message indicating that a note is too big and cannot be rendered")
.multilineTextAlignment(.center)
}
.foregroundColor(.secondary)
)
}
return AnyView(
items.reduce(Text(""), { (accumulated, item) in
return accumulated + item.render_to_text()
})
)
}
var attributed: AttributedString {
return items.reduce(AttributedString(stringLiteral: ""), { (accumulated, item) in
guard let item_attributed_string = item.attributed_string() else { return accumulated }
return accumulated + item_attributed_string
})
}
var items: [Item]
init() {
self.items = [.attributed_string(AttributedString(stringLiteral: ""))]
}
init(stringLiteral: String) {
self.items = [.attributed_string(AttributedString(stringLiteral: stringLiteral))]
}
init(attributed: AttributedString) {
self.items = [.attributed_string(attributed)]
}
init(items: [Item]) {
self.items = items
}
static func == (lhs: CompatibleText, rhs: CompatibleText) -> Bool {
return lhs.items == rhs.items
}
static func +(lhs: CompatibleText, rhs: CompatibleText) -> CompatibleText {
if case .attributed_string(let las) = lhs.items.last,
case .attributed_string(let ras) = rhs.items.first
{
// Concatenate attributed strings whenever possible to reduce item count
let combined_attributed_string = las + ras
return CompatibleText(items:
Array(lhs.items.prefix(upTo: lhs.items.count - 1)) +
[.attributed_string(combined_attributed_string)] +
Array(rhs.items.suffix(from: 1))
)
}
else {
return CompatibleText(items: lhs.items + rhs.items)
}
}
}
extension CompatibleText {
enum Item: Equatable {
case attributed_string(AttributedString)
case icon(named: String, offset: CGFloat)
func render_to_text() -> Text {
switch self {
case .attributed_string(let attributed_string):
return Text(attributed_string)
case .icon(named: let image_name, offset: let offset):
return Text(Image(image_name)).baselineOffset(offset)
}
}
func attributed_string() -> AttributedString? {
switch self {
case .attributed_string(let attributed_string):
return attributed_string
case .icon(named: let name, offset: _):
guard let img = UIImage(named: name) else { return nil }
return icon_attributed_string(img: img)
}
}
}
}
func icon_attributed_string(img: UIImage) -> AttributedString {
let attachment = NSTextAttachment()
attachment.image = img
let attachmentString = NSAttributedString(attachment: attachment)
return AttributedString(attachmentString)
}