Skip to content

Commit 42da0ae

Browse files
committed
Update AmityUIKit v4.18.0
- Support Xcode 26.4
1 parent 7e26185 commit 42da0ae

File tree

72 files changed

+1591
-466
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1591
-466
lines changed
Lines changed: 2 additions & 2 deletions
Loading

UpstraUIKit/AmityUIKit4/AmityUIKit4/Assets.xcassets/tagIcon.imageset/Contents.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"images" : [
33
{
4-
"filename" : "ic_tag.svg",
4+
"filename" : "ic_product_tag.svg",
55
"idiom" : "universal"
66
}
77
],
Lines changed: 3 additions & 0 deletions
Loading

UpstraUIKit/AmityUIKit4/AmityUIKit4/Assets.xcassets/tagIcon.imageset/ic_tag.svg

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "viewMoreReplyArrowIcon.svg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
}
12+
}
Lines changed: 3 additions & 0 deletions
Loading

UpstraUIKit/AmityUIKit4/AmityUIKit4/Chat/Components/Composer/Elements/AmityMessageTextEditorView.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ extension AmityMessageTextEditorView: AmityViewBuildable {
4444
public func scrollEnabled(_ value: Bool) -> Self {
4545
mutating(keyPath: \.scrollEnabled, value: value)
4646
}
47+
48+
public func displayInlineSuggestionView(_ value: Bool) -> Self {
49+
mutating(keyPath: \.displayInlineSuggestionView, value: value)
50+
}
4751
}
4852

4953
public struct AmityMessageTextEditorView: View {
@@ -70,6 +74,7 @@ public struct AmityMessageTextEditorView: View {
7074
private var maxHashtagCount: Int = 5
7175
private var enableProductMention: Bool = false
7276
private var scrollEnabled: Bool = true
77+
private var displayInlineSuggestionView: Bool = true
7378

7479
public init(_ viewModel: AmityTextEditorViewModel, text: Binding<String>, mentionData: Binding<MentionData>, mentionedUsers: Binding<[AmityMentionUserModel]>, links: Binding<[LinkDetail]?>? = nil, textViewHeight: CGFloat, textEditorMaxHeight: CGFloat = 106, placeholderPadding: CGFloat = 5) {
7580
self._text = text
@@ -185,14 +190,16 @@ public struct AmityMessageTextEditorView: View {
185190
}
186191
}
187192
.onChange(of: viewModel.showSuggestionView) { show in
188-
if show {
189-
showSuggestionOverlay()
190-
} else {
191-
SuggestionOverlayWindow.dismiss()
193+
if displayInlineSuggestionView {
194+
if show {
195+
showSuggestionOverlay()
196+
} else {
197+
SuggestionOverlayWindow.dismiss()
198+
}
192199
}
193200
}
194201
.onChange(of: viewModel.suggestionViewCursorRect) { newRect in
195-
if viewModel.showSuggestionView {
202+
if viewModel.showSuggestionView && displayInlineSuggestionView {
196203
SuggestionOverlayWindow.updatePosition(at: newRect)
197204
}
198205
}

UpstraUIKit/AmityUIKit4/AmityUIKit4/Clip/Page/Feed/ClipFeedItemOverlayView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ extension ClipFeedItemOverlayView {
425425
private var postExpandableContent: some View {
426426
ExpandableText(post.text, defaultAction: {
427427
togglePostContentExpantion()
428-
}, metadata: post.metadata, mentionees: post.mentionees, onTapMentionee: { userId in
428+
}, metadata: post.metadata, mentionees: post.mentionees, links: post.links, onTapMentionee: { userId in
429429
// We do not support expanding text in this view, so we redirect to post detail page
430430
onTapAction?(.userProfile(userId: userId))
431431
})

UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/CustomViews/AmityTextEditorView.swift

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ extension AmityTextEditorView: AmityViewBuildable {
5252
public func hightlightColor(_ value: UIColor) -> Self {
5353
mutating(keyPath: \.hightlightColor, value: value)
5454
}
55+
56+
public func enableLinkHighlight(_ value: Bool) -> Self {
57+
mutating(keyPath: \.enableLinkHighlight, value: value)
58+
}
5559
}
5660

5761

@@ -90,12 +94,16 @@ public struct AmityTextEditorView: View {
9094
private var backgroundColor: UIColor = .white
9195

9296
private var hightlightColor: UIColor = .blue
97+
private var enableLinkHighlight: Bool = false
9398

9499
public init(_ mentionManagerType: MentionManagerType, text: Binding<String>, mentionData: Binding<MentionData>, textViewHeight: CGFloat) {
95100
self._text = text
96101
self._mentionData = mentionData
97102
self._textEditorHeight = State(initialValue: textViewHeight)
98103
self.textEditorMinHeight = textViewHeight
104+
// Hide the placeholder immediately when the view is created with pre-existing text
105+
// (e.g. after a mention prefill triggers a .id()-based view recreation).
106+
self._hidePlaceholder = State(initialValue: !text.wrappedValue.isEmpty)
99107

100108
let mentionManger = MentionManager(withType: mentionManagerType)
101109

@@ -109,12 +117,28 @@ public struct AmityTextEditorView: View {
109117
.onAppear {
110118
textEditorInitialHeight = geometry.size.height
111119

120+
if let metadata = mentionData.metadata {
121+
viewModel.mentionManager.setMentions(metadata: metadata, inText: text)
122+
}
123+
124+
if enableLinkHighlight {
125+
applyLinkHighlight(to: viewModel.textView)
126+
}
127+
112128
if autoFocusTextEditor {
113-
viewModel.textView.becomeFirstResponder()
129+
DispatchQueue.main.async {
130+
viewModel.textView.becomeFirstResponder()
131+
let endPosition = viewModel.textView.endOfDocument
132+
viewModel.textView.selectedTextRange = viewModel.textView.textRange(from: endPosition, to: endPosition)
133+
}
114134
}
115135

116-
if let metadata = mentionData.metadata {
117-
viewModel.mentionManager.setMentions(metadata: metadata, inText: text)
136+
viewModel.didFinishMentionHighlight = {
137+
DispatchQueue.main.async {
138+
if enableLinkHighlight {
139+
self.applyLinkHighlight(to: self.viewModel.textView)
140+
}
141+
}
118142
}
119143
}
120144
.onChange(of: text) { value in
@@ -128,11 +152,19 @@ public struct AmityTextEditorView: View {
128152

129153
let paddedHeight = textHeight + defaultInset.top + defaultInset.bottom
130154
textEditorHeight = max(textEditorMinHeight, min(paddedHeight, textEditorMaxHeight))
155+
156+
if enableLinkHighlight {
157+
applyLinkHighlight(to: viewModel.textView)
158+
}
131159
}
132160
.onReceive(viewModel.textView.textPublisher, perform: { text in
133161
self.mentionData.metadata = viewModel.mentionManager.getMetadata()
134162
self.mentionData.mentionee = viewModel.mentionManager.getMentionees()
135163
self.text = text
164+
165+
if enableLinkHighlight {
166+
applyLinkHighlight(to: viewModel.textView)
167+
}
136168
})
137169
.onChange(of: mentionedUsers.count) { count in
138170
let listHeight = mentionedUsers.count < 5 ? CGFloat(mentionedUsers.count) * 50.0 : 250.0
@@ -202,6 +234,10 @@ public struct AmityTextEditorView: View {
202234
}
203235
.onReceive(viewConfig.$theme) { value in
204236
viewModel.updateAttributes(hightlightColor: value.primaryColor, textColor: value.baseColor)
237+
238+
if enableLinkHighlight {
239+
applyLinkHighlight(to: viewModel.textView)
240+
}
205241
}
206242
.frame(height: textEditorHeight)
207243
}
@@ -217,6 +253,33 @@ public struct AmityTextEditorView: View {
217253
0.0
218254
}
219255
}
256+
257+
private func applyLinkHighlight(to textView: UITextView) {
258+
guard let attributedText = textView.attributedText?.mutableCopy() as? NSMutableAttributedString else { return }
259+
260+
let selectedRange = textView.selectedRange
261+
let text = attributedText.string
262+
let fullRange = NSRange(location: 0, length: text.utf16.count)
263+
let extractedLinks = viewModel.linkManager.extractLinks(from: text)
264+
265+
// Remove existing link highlights
266+
attributedText.enumerateAttributes(in: fullRange) { attribute, range, _ in
267+
if attribute[.link] as? Bool == true {
268+
attributedText.removeAttribute(.foregroundColor, range: range)
269+
attributedText.removeAttribute(.link, range: range)
270+
}
271+
}
272+
273+
// Apply new link highlights
274+
for link in extractedLinks {
275+
attributedText.addAttribute(.foregroundColor, value: viewModel.highlightAttributes[.foregroundColor] ?? UIColor(), range: link.range)
276+
attributedText.addAttribute(.link, value: true, range: link.range)
277+
}
278+
279+
textView.attributedText = attributedText
280+
textView.selectedRange = selectedRange
281+
textView.typingAttributes = viewModel.typingAttributes
282+
}
220283
}
221284

222285
// MARK: - AmityTextEditorViewModel

UpstraUIKit/AmityUIKit4/AmityUIKit4/Core/Extenral/ExpandableText/ExpandableText.swift

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Foundation
99
import SwiftUI
10+
import SafariServices
1011
import AmitySDK
1112

1213
/**
@@ -58,13 +59,14 @@ public struct ExpandableText: View {
5859
internal var onTapProductTag: ((String) -> Void)?
5960
internal var defaultAction: (() -> Void)?
6061
internal var highlights: String?
62+
internal var links: [AmityLink] = []
6163

6264
/**
6365
Initializes a new `ExpandableText` instance with the specified text string, trimmed of any leading or trailing whitespace and newline characters.
6466
- Parameter text: The initial text string to display in the `ExpandableText` view.
6567
- Returns: A new `ExpandableText` instance with the specified text string and trimming applied.
6668
*/
67-
public init(_ text: String, defaultAction: (() -> Void)? = nil, metadata: [String: Any]? = nil, mentionees: [AmityMentionees]? = nil, productTags: [AmityProductTagModel]? = [], highlightedText: String? = nil, onTapMentionee: ((String) -> Void)? = nil, onTapHashtag: ((String) -> Void)? = nil, onTapProductTag: ((String) -> Void)? = nil) {
69+
public init(_ text: String, defaultAction: (() -> Void)? = nil, metadata: [String: Any]? = nil, mentionees: [AmityMentionees]? = nil, productTags: [AmityProductTagModel]? = [], highlightedText: String? = nil, links: [AmityLink] = [], onTapMentionee: ((String) -> Void)? = nil, onTapHashtag: ((String) -> Void)? = nil, onTapProductTag: ((String) -> Void)? = nil) {
6870
self.text = text.trimmingCharacters(in: .newlines)
6971
self.defaultAction = defaultAction
7072
self.metadata = metadata
@@ -74,6 +76,7 @@ public struct ExpandableText: View {
7476
self.onTapHashtag = onTapHashtag
7577
self.onTapProductTag = onTapProductTag
7678
self.highlights = highlightedText
79+
self.links = links
7780
}
7881

7982
public var body: some View {
@@ -131,8 +134,8 @@ public struct ExpandableText: View {
131134
@ViewBuilder
132135
private var content: some View {
133136
if #available(iOS 15, *) {
134-
let trimmedText = getAttributedText(text: textTrimmingDoubleNewlines, metadata: metadata ?? [:], mentionees: mentionees ?? [], productTags: productTags ?? [], font: .systemFont(ofSize: 14, weight: .bold), attributedColor: attributedColor, hashtagColor: hashtagColor, highlights: highlights, color: color)
135-
let text = getAttributedText(text: text, metadata: metadata ?? [:], mentionees: mentionees ?? [], productTags: productTags ?? [], font: .systemFont(ofSize: 14, weight: .bold), attributedColor: attributedColor, hashtagColor: hashtagColor, highlights: highlights, color: color)
137+
let trimmedText = getAttributedText(text: textTrimmingDoubleNewlines, metadata: metadata ?? [:], mentionees: mentionees ?? [], productTags: productTags ?? [], font: .systemFont(ofSize: 14, weight: .bold), attributedColor: attributedColor, hashtagColor: hashtagColor, highlights: highlights, color: color, links: links)
138+
let text = getAttributedText(text: text, metadata: metadata ?? [:], mentionees: mentionees ?? [], productTags: productTags ?? [], font: .systemFont(ofSize: 14, weight: .bold), attributedColor: attributedColor, hashtagColor: hashtagColor, highlights: highlights, color: color, links: links)
136139

137140
Text(trimMultipleNewlinesWhenTruncated
138141
? (shouldShowMoreButton ? trimmedText : text)
@@ -160,7 +163,11 @@ public struct ExpandableText: View {
160163
return .discarded
161164
}
162165

163-
return .systemAction
166+
// Open link in in-app browser
167+
let browserVC = SFSafariViewController(url: url)
168+
browserVC.modalPresentationStyle = .pageSheet
169+
UIApplication.topViewController()?.present(browserVC, animated: true)
170+
return .discarded
164171
})
165172
} else {
166173
Text(.init(
@@ -187,7 +194,7 @@ public struct ExpandableText: View {
187194

188195
extension ExpandableText {
189196
@available(iOS 15, *)
190-
private func getAttributedText(text: String, metadata: [String: Any], mentionees: [AmityMentionees], productTags: [AmityProductTagModel], font: UIFont, attributedColor: UIColor, hashtagColor: UIColor, highlights: String?, color: Color) -> AttributedString {
197+
private func getAttributedText(text: String, metadata: [String: Any], mentionees: [AmityMentionees], productTags: [AmityProductTagModel], font: UIFont, attributedColor: UIColor, hashtagColor: UIColor, highlights: String?, color: Color, links: [AmityLink] = []) -> AttributedString {
191198

192199
let highlightAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: attributedColor, .font: AmityTextStyle.bodyBold(.clear).getFont()]
193200

@@ -207,9 +214,27 @@ extension ExpandableText {
207214
highlightedText = AttributedString(attributedString)
208215

209216
// If links is present, highlight links
210-
let links = TextHighlighter.detectLinks(in: contentText)
211-
if !links.isEmpty {
212-
highlightedText = TextHighlighter.highlightLinks(links: links, in: highlightedText, attributes: highlightAttributes)
217+
var detectedLinks = TextHighlighter.detectLinks(in: contentText)
218+
219+
// Build a mapping from displayed link text -> actual URL from post.links
220+
var linkURLMap: [String: String] = [:]
221+
let nsText = contentText as NSString
222+
for link in links {
223+
if let index = link.index, let length = link.length,
224+
index >= 0, length > 0,
225+
index + length <= nsText.length {
226+
let linkText = nsText.substring(with: NSRange(location: index, length: length))
227+
linkURLMap[linkText] = link.url
228+
if !detectedLinks.contains(linkText) {
229+
detectedLinks.append(linkText)
230+
}
231+
} else if !detectedLinks.contains(link.url) {
232+
detectedLinks.append(link.url)
233+
}
234+
}
235+
236+
if !detectedLinks.isEmpty {
237+
highlightedText = TextHighlighter.highlightLinks(links: detectedLinks, in: highlightedText, attributes: highlightAttributes, linkURLMap: linkURLMap)
213238
}
214239

215240
// If the text need to highlight is a hashtag search keyword

0 commit comments

Comments
 (0)