Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion app/.package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "96354e940245cafaa647d9732b8970714f1940796fb057c444b7a34783652031",
"originHash" : "aab12e14249b1bb54284caab18d383aad59b04cef08757f6f257b61a1bf6f932",
"pins" : [
{
"identity" : "alerttoast",
Expand Down Expand Up @@ -72,6 +72,14 @@
"version" : "1.5.0"
}
},
{
"identity" : "richtext",
"kind" : "remoteSourceControl",
"location" : "https://github.com/LiYanan2004/RichText.git",
"state" : {
"revision" : "300582da5c632864598cf9e8d86618e101658afa"
}
},
{
"identity" : "sdwebimage",
"kind" : "remoteSourceControl",
Expand Down
2 changes: 2 additions & 0 deletions app/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ let project = Project(
.remote(url: "https://github.com/gh123man/LazyPager", requirement: .exact("1.1.13")),
.remote(url: "https://github.com/tevelee/SwiftUI-Flow", requirement: .exact("3.1.0")),
.remote(url: "https://github.com/SvenTiigi/WhatsNewKit.git", requirement: .exact("2.2.1")),
.remote(url: "https://github.com/LiYanan2004/RichText.git", requirement: .revision("300582da5c632864598cf9e8d86618e101658afa")),
],
targets: [
// iOS App Target
Expand Down Expand Up @@ -58,6 +59,7 @@ let project = Project(
.package(product: "LazyPager"),
.package(product: "Flow"),
.package(product: "WhatsNewKit"),
.package(product: "RichText"),
.xcframework(path: "../out/logic-ios.xcframework"),
],
settings: .settings(
Expand Down
1 change: 1 addition & 0 deletions app/Shared/Localization/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"Select a Forum in Sidebar" = "在边栏中选择一个版块";
"Select a Topic" = "选择一个话题";
"Post by" = "发表自";
"Post by " = "发表自 ";
"Filters" = "过滤";
"Hot Replies" = "热门回复";
"Collapsed Content" = "折叠的内容";
Expand Down
107 changes: 62 additions & 45 deletions app/Shared/Utilities/ContentCombiner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,23 @@

import Colorful
import Foundation
import RichText
import SwiftUI
import SwiftUIX

private extension TextContent {
// Prefer attributed string for better performance.
static func text(_ text: Text) -> Self {
TextContentBuilder.buildExpression(text)
}

static func attributedString(_ string: AttributedString) -> Self {
Self(.attributedString(string))
}
}

class ContentCombiner {
enum Subview {
case text(Text)
case text(TextContent)
case breakline
case other(AnyView)
}
Expand Down Expand Up @@ -145,27 +156,35 @@ class ContentCombiner {
self.postDate = postDate
}

private func styledText(_ text: Text, overridenFont: Font? = nil, overridenColor: Color? = nil) -> Text {
var text: Text = text
.font(overridenFont ?? font)
.foregroundColor(overridenColor ?? color)
private func styledText(_ text: Text) -> Text {
Text("⚠️\(text)⚠️")
}

private func styledString(_ string: String) -> AttributedString {
var string = AttributedString(string)
string.font = font
string.foregroundColor = color

if otherStyles.contains(.underline) {
text = text.underline()
string.underlineStyle = .single
}
if otherStyles.contains(.strikethrough) {
text = text.strikethrough()
string.strikethroughStyle = .single
}

return text
return string
}

private func append(symbol: Image) {
subviews.append(.text(.text(Text(symbol))))
}

private func append(_ view: some View) {
let subview: Subview

if view is Text {
let text = styledText(view as! Text)
subview = Subview.text(text)
subview = Subview.text(.text(text))
} else if view is AnyView {
subview = Subview.other(view as! AnyView)
} else {
Expand All @@ -175,57 +194,62 @@ class ContentCombiner {
subviews.append(subview)
}

private func append(_ string: String) {
let styledString = styledString(string)
subviews.append(.text(.attributedString(styledString)))
}

private func append(_ subview: Subview) {
subviews.append(subview)
}

private func build() -> Subview {
var textBuffer: Text?
var textBuffer = TextContent()
var results = [AnyView]()

func tryAppendTextBuffer() {
if let tb = textBuffer {
let view = tb.eraseToAnyView()
func tryFinishTextBuffer() {
if !textBuffer.fragments.isEmpty {
let view = TextView { textBuffer }.eraseToAnyView()
results.append(view)
textBuffer = nil
textBuffer = TextContent()
}
}

for subview in subviews {
switch subview {
case let .text(text):
textBuffer = (textBuffer ?? Text("")) + text
textBuffer += text
case .breakline:
if textBuffer != nil {
textBuffer = textBuffer! + Text("\n")
if !textBuffer.fragments.isEmpty {
textBuffer += .init(.string("\n"))
}
case let .other(view):
tryAppendTextBuffer()
tryFinishTextBuffer()
results.append(view)
}
}

if results.isEmpty {
// text-only view
return .text(textBuffer ?? Text(""))
return .text(textBuffer)
} else {
// complex view
tryAppendTextBuffer()
tryFinishTextBuffer()
let stack = VStack(alignment: alignment, spacing: 8) {
ForEach(results.indices, id: \.self) { index in
results[index]
.fixedSize(horizontal: false, vertical: true)
}
}
return .other(AnyView(stack))
return .other(stack.eraseToAnyView())
}
}

@ViewBuilder
func buildView() -> some View {
switch build() {
case let .text(text):
text
TextView { text }
case .breakline:
// not reached
EmptyView()
Expand Down Expand Up @@ -254,17 +278,15 @@ class ContentCombiner {
}

private func visit(plain: Span.Plain) {
let text: Text

var plain = plain
plain.text = plain.text.replacingOccurrences(of: "[*]", with: "→ ")

if plain.text == "Post by " {
text = Text("Post by") + Text(" ")
let string = if plain.text == "Post by " {
"Post by ".localized
} else {
text = Text(plain.text)
plain.text
}
append(text)
append(string)
}

private func visit(divider: Span.Tagged) {
Expand All @@ -281,19 +303,14 @@ class ContentCombiner {
private func visit(sticker: Span.Sticker) {
let name = sticker.name.replacingOccurrences(of: ":", with: "|")

let view: Text?
if let image = AppKitOrUIKitImage(named: name) {
if let image = PlatformImage(named: name) {
let renderingMode: Image.TemplateRenderingMode =
name.starts(with: "ac") || name.starts(with: "a2") ? .template : .original
view = Text(
Image(image: image)
.renderingMode(renderingMode)
)
append(symbol: Image(image: image).renderingMode(renderingMode))
} else {
view = Text("[🐶\(sticker.name)]").foregroundColor(.secondary)
// view = Text("[?\(sticker.name)]").foregroundColor(.secondary)
append("[?\(sticker.name)]")
}

append(view)
}

private func visit(tagged: Span.Tagged) {
Expand Down Expand Up @@ -435,7 +452,7 @@ class ContentCombiner {

private func visit(uid: Span.Tagged) {
let combiner = ContentCombiner(parent: self, color: { _ in Color.accentColor })
combiner.append(Text(Image(systemName: "person.fill")))
combiner.append(symbol: Image(systemName: "person.fill"))
combiner.visit(spans: uid.spans)

var name: String?
Expand All @@ -454,13 +471,13 @@ class ContentCombiner {

private func visit(pid: Span.Tagged) {
let combiner = ContentCombiner(parent: self, font: { $0?.bold() })
combiner.append(Text("Post"))
combiner.append("Post")
if pid.attributes.count > 2 {
let id = PostId.with {
$0.pid = pid.attributes[0]
$0.tid = pid.attributes[1]
}
combiner.append(Text(" #\(id.pid) "))
combiner.append(" #\(id.pid) ")
replyTo = id
}
append(combiner.build())
Expand Down Expand Up @@ -673,7 +690,7 @@ class ContentCombiner {
guard let fn = mnga.attributes.first else { return }
switch fn {
case "version":
append(Text(getVersionWithBuild()))
append(getVersionWithBuild())
default:
break
}
Expand All @@ -686,10 +703,10 @@ class ContentCombiner {
}

let combiner = ContentCombiner(parent: self)
let tagFont = Font.system(.footnote, design: .monospaced)
combiner.append(Text("[\(defaultTagged.tag)]").font(tagFont))
// let tagFont = Font.system(.footnote, design: .monospaced)
combiner.append("[\(defaultTagged.tag)]") // TODO: font
combiner.visit(spans: defaultTagged.spans)
combiner.append(Text("[/\(defaultTagged.tag)]").font(tagFont))
combiner.append("[/\(defaultTagged.tag)]") // TODO: font
append(combiner.build())
}
}
2 changes: 1 addition & 1 deletion app/Shared/Views/PostRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ struct PostRowView: View {
signature
}.padding(.vertical, 2)
.fixedSize(horizontal: false, vertical: true)
.contextMenu { menu }
// .contextMenu { menu } // interferes with text selection
#if os(iOS)
.listRowBackground(action?.scrollToPid == post.id.pid ? Color.tertiarySystemBackground : nil)
#endif
Expand Down
2 changes: 1 addition & 1 deletion app/Shared/Views/ShortMessagePostRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ struct ShortMessagePostRowView: View {
}.padding(.vertical, 2)
.fixedSize(horizontal: false, vertical: true)
#if os(macOS)
.contextMenu { menu }
// .contextMenu { menu } // interferes with text selection
#endif
}
}