Skip to content

Commit 111f6b0

Browse files
authored
Merge pull request #1007 from DimensionDev/ios/20250610
Refactoring code logic - 4
2 parents 3353785 + 0c65b0e commit 111f6b0

File tree

7 files changed

+528
-27
lines changed

7 files changed

+528
-27
lines changed

iosApp/iosApp.xcodeproj/project.pbxproj

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
06B851552B6B926D00DFA075 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 06B851542B6B926D00DFA075 /* Localizable.xcstrings */; };
2424
640236062DF6B62200C0D558 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 640236052DF6B62200C0D558 /* SwiftUIIntrospect */; };
2525
6404FB1E2DE45EB700D0A053 /* EmojiText in Frameworks */ = {isa = PBXBuildFile; productRef = 6404FB1D2DE45EB700D0A053 /* EmojiText */; };
26+
641DA51E2DF80F54008FD9BF /* MarkdownParser in Frameworks */ = {isa = PBXBuildFile; productRef = 641DA51D2DF80F54008FD9BF /* MarkdownParser */; };
27+
641DA5202DF80F54008FD9BF /* MarkdownView in Frameworks */ = {isa = PBXBuildFile; productRef = 641DA51F2DF80F54008FD9BF /* MarkdownView */; };
2628
642DED2D2DBF6965006B944C /* FontAwesomeSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 642DED2C2DBF6965006B944C /* FontAwesomeSwiftUI */; };
2729
644C84F82D535D0B00E92D0D /* JXPagingView in Frameworks */ = {isa = PBXBuildFile; productRef = 644C84F72D535D0B00E92D0D /* JXPagingView */; };
2830
64541A902DA3CE5A001208C7 /* Tiercel in Frameworks */ = {isa = PBXBuildFile; productRef = 64541A8F2DA3CE5A001208C7 /* Tiercel */; };
@@ -108,6 +110,7 @@
108110
64541A902DA3CE5A001208C7 /* Tiercel in Frameworks */,
109111
64E877CC2D0842C5003559B9 /* Fuzi in Frameworks */,
110112
646CABC52DAE876C00B9EA6F /* ExyteChat in Frameworks */,
113+
641DA5202DF80F54008FD9BF /* MarkdownView in Frameworks */,
111114
64E208712CF8458A001C64EF /* FLAnimatedImage in Frameworks */,
112115
642DED2D2DBF6965006B944C /* FontAwesomeSwiftUI in Frameworks */,
113116
0690507B2CAE634C007FC957 /* Awesome in Frameworks */,
@@ -136,6 +139,7 @@
136139
64E877C92D0842AA003559B9 /* SwiftSoup in Frameworks */,
137140
64E208772CF84612001C64EF /* JXPhotoBrowser in Frameworks */,
138141
066AD0472B3ACDC1006F28F4 /* DequeModule in Frameworks */,
142+
641DA51E2DF80F54008FD9BF /* MarkdownParser in Frameworks */,
139143
648EE5BD2D8D71EA00A457AA /* SwiftfulRouting in Frameworks */,
140144
);
141145
runOnlyForDeploymentPostprocessing = 0;
@@ -293,6 +297,8 @@
293297
645CC8662DC9FEF600B8CBF6 /* WishKit */,
294298
6404FB1D2DE45EB700D0A053 /* EmojiText */,
295299
640236052DF6B62200C0D558 /* SwiftUIIntrospect */,
300+
641DA51D2DF80F54008FD9BF /* MarkdownParser */,
301+
641DA51F2DF80F54008FD9BF /* MarkdownView */,
296302
);
297303
productName = iosApp;
298304
productReference = 7555FF7B242A565900829871 /* Flare.app */;
@@ -351,6 +357,7 @@
351357
645CC8652DC9FEF600B8CBF6 /* XCRemoteSwiftPackageReference "wishkit-ios" */,
352358
6404FB1C2DE45EB700D0A053 /* XCRemoteSwiftPackageReference "EmojiText" */,
353359
640236042DF6B62200C0D558 /* XCRemoteSwiftPackageReference "swiftui-introspect" */,
360+
641DA51C2DF80F53008FD9BF /* XCRemoteSwiftPackageReference "MarkdownView" */,
354361
);
355362
preferredProjectObjectVersion = 50;
356363
productRefGroup = 7555FF7C242A565900829871 /* Products */;
@@ -766,6 +773,14 @@
766773
minimumVersion = 4.2.1;
767774
};
768775
};
776+
641DA51C2DF80F53008FD9BF /* XCRemoteSwiftPackageReference "MarkdownView" */ = {
777+
isa = XCRemoteSwiftPackageReference;
778+
repositoryURL = "https://github.com/Lakr233/MarkdownView";
779+
requirement = {
780+
kind = upToNextMajorVersion;
781+
minimumVersion = 2.0.9;
782+
};
783+
};
769784
642DED2B2DBF6965006B944C /* XCRemoteSwiftPackageReference "FontAwesomeSwiftUI" */ = {
770785
isa = XCRemoteSwiftPackageReference;
771786
repositoryURL = "https://github.com/onmyway133/FontAwesomeSwiftUI";
@@ -966,6 +981,16 @@
966981
package = 6404FB1C2DE45EB700D0A053 /* XCRemoteSwiftPackageReference "EmojiText" */;
967982
productName = EmojiText;
968983
};
984+
641DA51D2DF80F54008FD9BF /* MarkdownParser */ = {
985+
isa = XCSwiftPackageProductDependency;
986+
package = 641DA51C2DF80F53008FD9BF /* XCRemoteSwiftPackageReference "MarkdownView" */;
987+
productName = MarkdownParser;
988+
};
989+
641DA51F2DF80F54008FD9BF /* MarkdownView */ = {
990+
isa = XCSwiftPackageProductDependency;
991+
package = 641DA51C2DF80F53008FD9BF /* XCRemoteSwiftPackageReference "MarkdownView" */;
992+
productName = MarkdownView;
993+
};
969994
642DED2C2DBF6965006B944C /* FontAwesomeSwiftUI */ = {
970995
isa = XCSwiftPackageProductDependency;
971996
package = 642DED2B2DBF6965006B944C /* XCRemoteSwiftPackageReference "FontAwesomeSwiftUI" */;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
import SwiftUI
3+
import EmojiText
4+
5+
@MainActor
6+
public struct FlareEmojiText: View {
7+
private let text: String
8+
private let markdownText: String
9+
private let emojis: [RemoteEmoji]
10+
private let style: FlareTextStyle.Style
11+
private let isRTL: Bool
12+
private let fontScale: Double
13+
private let lineSpacing: Double
14+
private var linkHandler: ((URL) -> Void)?
15+
@Environment(FlareTheme.self) private var theme
16+
17+
public init(
18+
text: String,
19+
markdownText: String,
20+
emojis: [RemoteEmoji] = [],
21+
style: FlareTextStyle.Style,
22+
isRTL: Bool = false,
23+
fontScale: Double = 1.0,
24+
lineSpacing: Double = 1.0
25+
) {
26+
self.text = text
27+
self.markdownText = markdownText
28+
self.emojis = emojis
29+
self.style = style
30+
self.isRTL = isRTL
31+
self.fontScale = fontScale
32+
self.lineSpacing = lineSpacing
33+
}
34+
35+
public func onLinkTap(_ handler: @escaping (URL) -> Void) -> FlareEmojiText {
36+
var view = self
37+
view.linkHandler = handler
38+
return view
39+
}
40+
41+
public var body: some View {
42+
EmojiText(markdown: markdownText.replacingOccurrences(
43+
of: "<br\\s*/?>",
44+
with: "\n",
45+
options: [.regularExpression, .caseInsensitive]
46+
), emojis: emojis)
47+
.markdownTheme(.flareMarkdownStyle(using: style, fontScale: theme.fontSizeScale))
48+
// .font(style.font.scaled(by: fontScale))
49+
// .foregroundColor(style.color)
50+
.multilineTextAlignment(.leading)
51+
.fixedSize(horizontal: false, vertical: true)
52+
.relativeLineSpacing(.em(lineSpacing - 1.0))
53+
.environment(\.layoutDirection, isRTL ? .rightToLeft : .leftToRight)
54+
.environment(\.openURL, OpenURLAction { url in
55+
if let handler = linkHandler {
56+
handler(url)
57+
return .handled
58+
} else {
59+
return .systemAction
60+
}
61+
})
62+
}
63+
}
64+
Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import EmojiText
12
import MarkdownUI
23
import SwiftUI
34
import TwitterText
@@ -11,6 +12,8 @@ public struct FlareText: View {
1112
@Environment(FlareTheme.self) private var theme
1213
@Environment(\.appSettings) private var appSettings
1314

15+
@State private var cacheKey: String = ""
16+
1417
public init(
1518
_ text: String,
1619
_ markdownText: String,
@@ -29,33 +32,101 @@ public struct FlareText: View {
2932
return view
3033
}
3134

35+
@ViewBuilder
36+
private var optimizedBody: some View {
37+
switch appSettings.appearanceSettings.renderEngine {
38+
case .markdown:
39+
MarkdownRenderer()
40+
case .flareText:
41+
FlareTextRenderer()
42+
case .textViewMarkdown:
43+
TextViewMarkdownRenderer()
44+
case .emojiText:
45+
EmojiTextRenderer()
46+
}
47+
}
48+
49+
@ViewBuilder
50+
private func MarkdownRenderer() -> some View {
51+
Markdown(markdownText)
52+
.markdownTheme(.flareMarkdownStyle(using: style, fontScale: theme.fontSizeScale))
53+
.markdownInlineImageProvider(.emoji)
54+
.relativeLineSpacing(.em(theme.lineSpacing - 1.0)) // 转换为相对行高
55+
.padding(.vertical, 4)
56+
.environment(\.layoutDirection, isRTL ? .rightToLeft : .leftToRight)
57+
.environment(\.openURL, linkOpenURLAction)
58+
}
59+
60+
@ViewBuilder
61+
private func FlareTextRenderer() -> some View {
62+
let currentCacheKey = FlareTextCache.shared.generateCacheKey(
63+
text: text,
64+
markdownText: markdownText,
65+
style: style,
66+
renderEngine: appSettings.appearanceSettings.renderEngine
67+
)
68+
Text(AttributedString(processText(text, markdownText)))
69+
.multilineTextAlignment(.leading)
70+
.fixedSize(horizontal: false, vertical: true)
71+
// .environment(
72+
// \.openURL,
73+
// OpenURLAction { url in
74+
// if let handler = linkHandler {
75+
// handler(url)
76+
// }
77+
// return .handled
78+
// }
79+
// )
80+
.environment(\.openURL, linkOpenURLAction)
81+
.environment(\.layoutDirection, isRTL ? .rightToLeft : .leftToRight)
82+
.onAppear {
83+
self.cacheKey = currentCacheKey
84+
}
85+
.onChange(of: currentCacheKey) { newKey in
86+
self.cacheKey = newKey
87+
}
88+
}
89+
90+
@ViewBuilder
91+
private func TextViewMarkdownRenderer() -> some View {
92+
TextViewMarkdown(
93+
markdownText: markdownText,
94+
style: style,
95+
fontScale: theme.fontSizeScale
96+
)
97+
}
98+
99+
@ViewBuilder
100+
private func EmojiTextRenderer() -> some View {
101+
FlareEmojiText(
102+
text: text,
103+
markdownText: markdownText,
104+
emojis: [],
105+
style: style,
106+
isRTL: isRTL,
107+
fontScale: theme.fontSizeScale,
108+
lineSpacing: theme.lineSpacing
109+
)
110+
// .environment(\.openURL, linkOpenURLAction)
111+
.onLinkTap { url in
112+
if let handler = linkHandler {
113+
handler(url)
114+
}
115+
}
116+
}
117+
32118
public var body: some View {
33-
if appSettings.appearanceSettings.renderEngine == .markdown {
34-
Markdown(markdownText)
35-
.markdownTheme(.flareMarkdownStyle(using: style, fontScale: theme.fontSizeScale))
36-
.markdownInlineImageProvider(.emoji)
37-
.relativeLineSpacing(.em(theme.lineSpacing - 1.0)) // 转换为相对行高
38-
.padding(.vertical, 4)
39-
.environment(\.layoutDirection, isRTL ? .rightToLeft : .leftToRight)
40-
.environment(\.openURL, OpenURLAction { url in
41-
if let handler = linkHandler {
42-
handler(url)
43-
return .handled
44-
} else {
45-
return .systemAction
46-
}
47-
})
48-
} else {
49-
Text(AttributedString(processText(text, markdownText)))
50-
.multilineTextAlignment(.leading)
51-
.fixedSize(horizontal: false, vertical: true)
52-
.environment(\.openURL, OpenURLAction { url in
53-
if let handler = linkHandler {
54-
handler(url)
55-
}
56-
return .handled
57-
})
58-
.environment(\.layoutDirection, isRTL ? .rightToLeft : .leftToRight)
119+
optimizedBody
120+
}
121+
122+
private var linkOpenURLAction: OpenURLAction {
123+
OpenURLAction { url in
124+
if let handler = linkHandler {
125+
handler(url)
126+
return .handled
127+
} else {
128+
return .systemAction
129+
}
59130
}
60131
}
61132

@@ -67,4 +138,25 @@ public struct FlareText: View {
67138
)
68139
return NSAttributedString(attributedString)
69140
}
70-
}
141+
142+
private func getCachedOrProcessText(cacheKey: String) -> NSAttributedString {
143+
144+
if let cached = FlareTextCache.shared.getCachedText(for: cacheKey) {
145+
return cached
146+
}
147+
148+
149+
let attributedString = FlareTextStyle.attributeString(
150+
of: text,
151+
markdownText: markdownText,
152+
style: style
153+
)
154+
let nsAttributedString = NSAttributedString(attributedString)
155+
156+
157+
FlareTextCache.shared.setCachedText(nsAttributedString, for: cacheKey)
158+
159+
return nsAttributedString
160+
}
161+
162+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
import SwiftUI
3+
import TwitterText
4+
5+
6+
class FlareTextCache {
7+
static let shared = FlareTextCache()
8+
private let cache = NSCache<NSString, NSAttributedString>()
9+
private let queue = DispatchQueue(label: "flare.text.cache", qos: .utility)
10+
11+
private init() {
12+
cache.countLimit = 500
13+
cache.totalCostLimit = 50 * 1024 * 1024
14+
}
15+
16+
func getCachedText(for key: String) -> NSAttributedString? {
17+
return cache.object(forKey: NSString(string: key))
18+
}
19+
20+
func setCachedText(_ attributedString: NSAttributedString, for key: String) {
21+
let cost = attributedString.length * 2
22+
cache.setObject(attributedString, forKey: NSString(string: key), cost: cost)
23+
}
24+
25+
func generateCacheKey(text: String, markdownText: String, style: FlareTextStyle.Style, renderEngine: RenderEngine) -> String {
26+
let styleHash = "\(style.font.pointSize)_\(style.textColor.description)"
27+
return "\(text.hashValue)_\(markdownText.hashValue)_\(styleHash)"
28+
}
29+
}

0 commit comments

Comments
 (0)