Skip to content

Commit a13d411

Browse files
bielikbevsaev
andauthored
[CIS-1800] Make link preview tappable for URLs missing the URL scheme (#2106)
* [CIS-1800] Link Preview is now tappable Co-authored-by: Pavel Evsaev <[email protected]>
1 parent d2cc7fa commit a13d411

File tree

10 files changed

+147
-10
lines changed

10 files changed

+147
-10
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1616
- Fix Logger persisting config after usage, preventing changing parameters (such as LogLevel) [#2081](https://github.com/GetStream/stream-chat-swift/issues/2081)
1717
- Fix crash in `ChannelVC` when it's initialized using a `ChannelController` created with `createDirectMessageChannelWith` factory [#2097](https://github.com/GetStream/stream-chat-swift/issues/2097)
1818
- Fix `ChannelListSortingKey.unreadCount` causing database crash [#2094](https://github.com/GetStream/stream-chat-swift/issues/2094)
19+
- Fix attachment link previews with missing URL scheme not opening in browser [#2106](https://github.com/GetStream/stream-chat-swift/pull/2106)
20+
1921
### 🔄 Changed
2022
- JSON decoding performance is increased 3 times, parsing time reduced by %70 [#2081](https://github.com/GetStream/stream-chat-swift/issues/2081)
2123
- EventPayload decoding errors are now more verbose [#2099](https://github.com/GetStream/stream-chat-swift/issues/2099)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// Copyright © 2022 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
7+
extension URL {
8+
/// Enriches `URL` with `http` scheme if it's missing
9+
var enrichedURL: URL {
10+
guard scheme == nil else {
11+
return self
12+
}
13+
14+
return URL(string: "http://" + absoluteString) ?? self
15+
}
16+
}

Sources/StreamChat/Models/Attachments/ChatMessageLinkAttachment.swift

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public struct LinkAttachmentPayload: AttachmentPayload {
2626
/// A link for displaying an attachment.
2727
/// Can be different from the original link, depends on the enriching rules.
2828
public var titleLink: URL?
29+
// A link for navigating to url. This computed fallbacks to `titleLink` or `originalURL` and enriches it with URL scheme if needed.
30+
// e.g "google.com" -> "http://google.com"
31+
public var url: URL {
32+
titleLink?.enrichedURL ?? originalURL.enrichedURL
33+
}
34+
2935
/// An image.
3036
public var assetURL: URL?
3137
/// A preview image URL.

Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ open class ChatMessageListVC: _ViewController,
540540
_ attachment: ChatMessageLinkAttachment,
541541
at indexPath: IndexPath?
542542
) {
543-
router.showLinkPreview(link: attachment.originalURL)
543+
router.showLinkPreview(link: attachment.url)
544544
}
545545

546546
open func didTapOnAttachment(

Sources/StreamChatUI/Navigation/ChatMessageListRouter.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ open class ChatMessageListRouter:
108108
///
109109
@available(iOSApplicationExtension, unavailable)
110110
open func showLinkPreview(link: URL) {
111-
UIApplication.shared.open(link)
111+
UIApplication.shared.open(link) { success in
112+
guard success == false else { return }
113+
log.error("Failed to open URL from link preview: \(link)")
114+
}
112115
}
113116

114117
/// Shows a View Controller that show the detail of a file attachment.

StreamChat.xcodeproj/project.pbxproj

+36-8
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,9 @@
798798
A3227E7A284A4CE000EBE6CC /* DemoChatChannelListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3227E79284A4CE000EBE6CC /* DemoChatChannelListVC.swift */; };
799799
A3227E7E284A511200EBE6CC /* DemoAppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3227E7D284A511200EBE6CC /* DemoAppConfiguration.swift */; };
800800
A3227EC9284A52EE00EBE6CC /* PushNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3227EC8284A52EE00EBE6CC /* PushNotifications.swift */; };
801+
A32D55142860B40B00E66AF9 /* ChatMessageLinkAttachment_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A32D55132860B40B00E66AF9 /* ChatMessageLinkAttachment_Tests.swift */; };
802+
A32D55162860B54700E66AF9 /* AttachmentPayloadLink_without_title_link.json in Resources */ = {isa = PBXBuildFile; fileRef = A32D55152860B54700E66AF9 /* AttachmentPayloadLink_without_title_link.json */; };
803+
A32D55182860B70200E66AF9 /* AttachmentPayloadLink_with_title_link.json in Resources */ = {isa = PBXBuildFile; fileRef = A32D55172860B70200E66AF9 /* AttachmentPayloadLink_with_title_link.json */; };
801804
A33FA816282D595C00DC40E8 /* ChannelList_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33FA815282D595C00DC40E8 /* ChannelList_Tests.swift */; };
802805
A33FA818282E559A00DC40E8 /* SlowMode_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33FA817282E559A00DC40E8 /* SlowMode_Tests.swift */; };
803806
A344077427D753530044F150 /* ChannelUnreadCount_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A344075027D753530044F150 /* ChannelUnreadCount_Mock.swift */; };
@@ -853,6 +856,8 @@
853856
A368E71627F33E16009063C1 /* MissingEventsPayload-IncompleteChannel.json in Resources */ = {isa = PBXBuildFile; fileRef = C1CE8EFD27F20C3A0091097B /* MissingEventsPayload-IncompleteChannel.json */; };
854857
A3698D802820187200814143 /* DebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3698D7F2820187200814143 /* DebugMenu.swift */; };
855858
A3698DD828215E2F00814143 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3698DD728215E2F00814143 /* Settings.swift */; };
859+
A36C39F52860680A0004EB7E /* URL+EnrichedURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36C39F42860680A0004EB7E /* URL+EnrichedURL.swift */; };
860+
A36C39F828606B5D0004EB7E /* URL_EnrichedURL_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36C39F728606B5D0004EB7E /* URL_EnrichedURL_Tests.swift */; };
856861
A36D1EA9283F755F008D6110 /* StreamTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AD02BC27D8E44B000611B7 /* StreamTestCase.swift */; };
857862
A36F997A2818459C0078260D /* InternetConnectionMonitor_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36F99792818459C0078260D /* InternetConnectionMonitor_Mock.swift */; };
858863
A3813B4C2825C8030076E838 /* CustomChatMessageListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3813B4B2825C8030076E838 /* CustomChatMessageListRouter.swift */; };
@@ -3088,6 +3093,9 @@
30883093
A3227E79284A4CE000EBE6CC /* DemoChatChannelListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoChatChannelListVC.swift; sourceTree = "<group>"; };
30893094
A3227E7D284A511200EBE6CC /* DemoAppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoAppConfiguration.swift; sourceTree = "<group>"; };
30903095
A3227EC8284A52EE00EBE6CC /* PushNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotifications.swift; sourceTree = "<group>"; };
3096+
A32D55132860B40B00E66AF9 /* ChatMessageLinkAttachment_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageLinkAttachment_Tests.swift; sourceTree = "<group>"; };
3097+
A32D55152860B54700E66AF9 /* AttachmentPayloadLink_without_title_link.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AttachmentPayloadLink_without_title_link.json; sourceTree = "<group>"; };
3098+
A32D55172860B70200E66AF9 /* AttachmentPayloadLink_with_title_link.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AttachmentPayloadLink_with_title_link.json; sourceTree = "<group>"; };
30913099
A33FA815282D595C00DC40E8 /* ChannelList_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelList_Tests.swift; sourceTree = "<group>"; };
30923100
A33FA817282E559A00DC40E8 /* SlowMode_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlowMode_Tests.swift; sourceTree = "<group>"; };
30933101
A344074E27D753530044F150 /* ChatClientUpdater_Mock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatClientUpdater_Mock.swift; sourceTree = "<group>"; };
@@ -3144,6 +3152,8 @@
31443152
A3600B3B283F639700E1C930 /* StreamTestCase+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StreamTestCase+Tags.swift"; sourceTree = "<group>"; };
31453153
A3698D7F2820187200814143 /* DebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugMenu.swift; sourceTree = "<group>"; };
31463154
A3698DD728215E2F00814143 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
3155+
A36C39F42860680A0004EB7E /* URL+EnrichedURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+EnrichedURL.swift"; sourceTree = "<group>"; };
3156+
A36C39F728606B5D0004EB7E /* URL_EnrichedURL_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL_EnrichedURL_Tests.swift; sourceTree = "<group>"; };
31473157
A36F99792818459C0078260D /* InternetConnectionMonitor_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetConnectionMonitor_Mock.swift; sourceTree = "<group>"; };
31483158
A3813B49282595960076E838 /* ChannelConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelConfig.swift; sourceTree = "<group>"; };
31493159
A3813B4B2825C8030076E838 /* CustomChatMessageListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomChatMessageListRouter.swift; sourceTree = "<group>"; };
@@ -3937,6 +3947,7 @@
39373947
799C944A247D574F001F1104 /* Controllers */,
39383948
799C9429247D2FB9001F1104 /* Database */,
39393949
79280F3D2484E33C00CDEB89 /* Errors */,
3950+
A36C39F3286067F90004EB7E /* Extensions */,
39403951
AD7292EB25F6C8C500ED2150 /* Generated */,
39413952
799C9430247D2FB9001F1104 /* Models */,
39423953
792A4F412480103A00EAF71D /* Query */,
@@ -4020,6 +4031,7 @@
40204031
A364D0A527D127E00029857A /* Controllers */,
40214032
A364D09B27D0C6640029857A /* Database */,
40224033
A364D0BA27D12ABD0029857A /* Errors */,
4034+
A36C39F628606B3A0004EB7E /* Extensions */,
40234035
A364D0A027D0C8690029857A /* Models */,
40244036
A364D0B727D12A520029857A /* Query */,
40254037
A364D0A327D126490029857A /* Repositories */,
@@ -4352,13 +4364,6 @@
43524364
path = MessageSearchController;
43534365
sourceTree = "<group>";
43544366
};
4355-
798779F52498E47700015F8B /* MockEndpointResponses */ = {
4356-
isa = PBXGroup;
4357-
children = (
4358-
);
4359-
path = MockEndpointResponses;
4360-
sourceTree = "<group>";
4361-
};
43624367
79877A122498E4EE00015F8B /* Endpoints */ = {
43634368
isa = PBXGroup;
43644369
children = (
@@ -5838,7 +5843,6 @@
58385843
A364D08727CFBA3F0029857A /* Mocks */ = {
58395844
isa = PBXGroup;
58405845
children = (
5841-
798779F52498E47700015F8B /* MockEndpointResponses */,
58425846
A344074F27D753530044F150 /* Models + Extensions */,
58435847
A364D09027D0BE660029857A /* StreamChat */,
58445848
);
@@ -6155,6 +6159,7 @@
61556159
843C53AE2693759E00C7D8EA /* FileAttachmentPayload_Tests.swift */,
61566160
843C53AA269370A900C7D8EA /* ImageAttachmentPayload_Tests.swift */,
61576161
843C53AC269373EA00C7D8EA /* VideoAttachmentPayload_Tests.swift */,
6162+
A32D55132860B40B00E66AF9 /* ChatMessageLinkAttachment_Tests.swift */,
61586163
);
61596164
path = Attachments;
61606165
sourceTree = "<group>";
@@ -6452,6 +6457,22 @@
64526457
path = Operations;
64536458
sourceTree = "<group>";
64546459
};
6460+
A36C39F3286067F90004EB7E /* Extensions */ = {
6461+
isa = PBXGroup;
6462+
children = (
6463+
A36C39F42860680A0004EB7E /* URL+EnrichedURL.swift */,
6464+
);
6465+
path = Extensions;
6466+
sourceTree = "<group>";
6467+
};
6468+
A36C39F628606B3A0004EB7E /* Extensions */ = {
6469+
isa = PBXGroup;
6470+
children = (
6471+
A36C39F728606B5D0004EB7E /* URL_EnrichedURL_Tests.swift */,
6472+
);
6473+
path = Extensions;
6474+
sourceTree = "<group>";
6475+
};
64556476
A36D1EAB283F8EDD008D6110 /* TestTools */ = {
64566477
isa = PBXGroup;
64576478
children = (
@@ -7720,6 +7741,8 @@
77207741
E73BD9E7264C015200E208B7 /* AttachmentPayloadGiphyWithActions.json */,
77217742
E73BD9E8264C016400E208B7 /* AttachmentPayloadGiphyWithoutActions.json */,
77227743
2289852725CC0332007F2C26 /* AttachmentPayloadImage.json */,
7744+
A32D55172860B70200E66AF9 /* AttachmentPayloadLink_with_title_link.json */,
7745+
A32D55152860B54700E66AF9 /* AttachmentPayloadLink_without_title_link.json */,
77237746
2289852525CC0331007F2C26 /* AttachmentPayloadLink.json */,
77247747
AC73783926A6AF1C002ED7B4 /* AttachmentPayloadLinkWithoutImagePreview.json */,
77257748
);
@@ -8648,6 +8671,7 @@
86488671
A311B3F327E8B99800CFCF6D /* ChannelVisible.json in Resources */,
86498672
A311B42127E8B9C900CFCF6D /* GuestUser+InvalidToken.json in Resources */,
86508673
A311B41427E8B9B900CFCF6D /* UserStopWatching.json in Resources */,
8674+
A32D55182860B70200E66AF9 /* AttachmentPayloadLink_with_title_link.json in Resources */,
86518675
A311B3F227E8B99800CFCF6D /* ChannelHidden.json in Resources */,
86528676
A311B3EB27E8B99200CFCF6D /* AttachmentPayload+NoType.json in Resources */,
86538677
A311B3F827E8B9A200CFCF6D /* MemberRemoved.json in Resources */,
@@ -8674,6 +8698,7 @@
86748698
A311B3FB27E8B9A800CFCF6D /* MessageNew+MissingFields.json in Resources */,
86758699
A311B40027E8B9A800CFCF6D /* MessageDeleted+MissingUser.json in Resources */,
86768700
A311B40827E8B9AD00CFCF6D /* NotificationAddedToChannel+MissingFields.json in Resources */,
8701+
A32D55162860B54700E66AF9 /* AttachmentPayloadLink_without_title_link.json in Resources */,
86778702
A311B40527E8B9AD00CFCF6D /* NotificationInvited.json in Resources */,
86788703
A311B42527E8B9CE00CFCF6D /* MessageReactionsPayload.json in Resources */,
86798704
A311B41C27E8B9BE00CFCF6D /* FlagMessagePayload+CustomExtraData.json in Resources */,
@@ -9740,6 +9765,7 @@
97409765
792A4F482480107A00EAF71D /* Pagination.swift in Sources */,
97419766
79AF43B42632AF1C00E75CDA /* ChannelVisibilityEventMiddleware.swift in Sources */,
97429767
DAF1BED525066114003CEDC0 /* MessageController+Combine.swift in Sources */,
9768+
A36C39F52860680A0004EB7E /* URL+EnrichedURL.swift in Sources */,
97439769
C1FC2F9B2742579D0062530F /* FrameCollector.swift in Sources */,
97449770
8A0C3BBC24C0947400CAFD19 /* UserEvents.swift in Sources */,
97459771
DA4EE5B2252B67F500CB26D4 /* UserListController+SwiftUI.swift in Sources */,
@@ -9881,6 +9907,7 @@
98819907
84ABF69A274E570600EDDA68 /* EventBatcher_Tests.swift in Sources */,
98829908
DA4971542549C2A000AC68C2 /* MessageAttachmentPayload_Tests.swift in Sources */,
98839909
84D5BC59277B188E00A65C75 /* PinnedMessagesPagination_Tests.swift in Sources */,
9910+
A36C39F828606B5D0004EB7E /* URL_EnrichedURL_Tests.swift in Sources */,
98849911
888E8C51252B4BAB00195E03 /* ChannelMemberBanRequestPayload_Tests.swift in Sources */,
98859912
791D3D9026776BE400E3A0F9 /* ChannelMemberListSortingKey_Tests.swift in Sources */,
98869913
A382131E2805C8AC0068D30E /* TestsEnvironmentSetup.swift in Sources */,
@@ -9981,6 +10008,7 @@
998110008
A3C7BAE527E4EABC00BBF4FA /* ChannelEvents_IntegrationTests.swift in Sources */,
998210009
7952B3B324D4560E00AC53D4 /* ChannelController_Tests.swift in Sources */,
998310010
8A0CC9EB24C601F600705CF9 /* MemberEvents_Tests.swift in Sources */,
10011+
A32D55142860B40B00E66AF9 /* ChatMessageLinkAttachment_Tests.swift in Sources */,
998410012
792B805224D95D4300C2963E /* Cached_StressTests.swift in Sources */,
998510013
A34ECB4A27F5CA1B00A804C1 /* TypingEvents_IntegrationTests.swift in Sources */,
998610014
8A62705C24BE2BC00040BFD6 /* TypingEvent_Tests.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"og_scrape_url" : "https://www.google.com",
3+
"title_link" : "https://www.google.com",
4+
"title" : "Google",
5+
"text" : "Google",
6+
"thumb_url" : "https:\/\/i.ytimg.com\/vi\/ZXBcwyMUrcU\/maxresdefault_preview.jpg",
7+
"asset_url" : "https:\/\/www.youtube.com\/embed\/ZXBcwyMUrcU"
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"og_scrape_url" : "google.com",
3+
"title" : "Google",
4+
"text" : "Google",
5+
"thumb_url" : "https:\/\/i.ytimg.com\/vi\/ZXBcwyMUrcU\/maxresdefault_preview.jpg",
6+
"asset_url" : "https:\/\/www.youtube.com\/embed\/ZXBcwyMUrcU"
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// Copyright © 2022 Stream.io Inc. All rights reserved.
3+
//
4+
5+
@testable import StreamChat
6+
import XCTest
7+
8+
final class URL_EnrichedURL_Tests: XCTestCase {
9+
func test_schemeIsAdded_whenMissing() {
10+
// GIVEN
11+
let url = URL(string: "google.com")
12+
13+
// THEN
14+
XCTAssertEqual(url?.enrichedURL.absoluteString, "http://google.com")
15+
}
16+
17+
func test_urlWithSchemeIsNotChanged_whenHavingScheme() {
18+
// GIVEN
19+
let url = URL(string: "https://google.com")
20+
21+
// THEN
22+
XCTAssertEqual(url?.enrichedURL, url)
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// Copyright © 2022 Stream.io Inc. All rights reserved.
3+
//
4+
5+
@testable import StreamChat
6+
import XCTest
7+
8+
final class ChatMessageLinkAttachment_Tests: XCTestCase {
9+
func test_hasValidURL_whenTitleLinkIsMissingInPayload() {
10+
// GIVEN
11+
let json = XCTestCase.mockData(fromJSONFile: "AttachmentPayloadLink_without_title_link")
12+
13+
do {
14+
// WHEN
15+
let payload = try JSONDecoder.default.decode(LinkAttachmentPayload.self, from: json)
16+
17+
// THEN
18+
XCTAssertEqual(payload.originalURL, URL(string: "google.com"))
19+
XCTAssertNil(payload.titleLink)
20+
XCTAssertEqual(payload.url.absoluteString, "http://google.com")
21+
} catch {
22+
XCTFail(error.localizedDescription)
23+
}
24+
}
25+
26+
func test_hasValidURL_whenTitleLinkIsInPayload() {
27+
// GIVEN
28+
let json = XCTestCase.mockData(fromJSONFile: "AttachmentPayloadLink_with_title_link")
29+
30+
do {
31+
// WHEN
32+
let payload = try JSONDecoder.default.decode(LinkAttachmentPayload.self, from: json)
33+
34+
// THEN
35+
let expectedURL = URL(string: "https://www.google.com")
36+
XCTAssertEqual(payload.originalURL, expectedURL)
37+
XCTAssertEqual(payload.titleLink, expectedURL)
38+
XCTAssertEqual(payload.url.absoluteString, "https://www.google.com")
39+
} catch {
40+
XCTFail(error.localizedDescription)
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)