diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index f3bfd8886..4d237ffe9 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ 503422582AAB798600EFE88D /* AppFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503422572AAB798600EFE88D /* AppFlow.swift */; }; 503BA26F2A2C94540052516C /* URL+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503BA26E2A2C94540052516C /* URL+Identifiable.swift */; }; 504106CD2A744D7F000AAEF8 /* CommentRepository+Dependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504106CC2A744D7F000AAEF8 /* CommentRepository+Dependency.swift */; }; + 504ECBAE2AB45B2A006C0B96 /* LemmyURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504ECBAD2AB45B2A006C0B96 /* LemmyURL.swift */; }; + 504ECBB12AB4B101006C0B96 /* LemmyURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504ECBB02AB4B101006C0B96 /* LemmyURLTests.swift */; }; 505240E32A86916500EA4558 /* FavoriteCommunitiesTracker+Dependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505240E22A86916500EA4558 /* FavoriteCommunitiesTracker+Dependency.swift */; }; 505240E52A86E32700EA4558 /* CommunityListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505240E42A86E32700EA4558 /* CommunityListModel.swift */; }; 505240E72A88D36D00EA4558 /* SectionIndexTitles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505240E62A88D36D00EA4558 /* SectionIndexTitles.swift */; }; @@ -94,7 +96,6 @@ 50D61E5B2AA32B9400A926EC /* APISession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D61E5A2AA32B9400A926EC /* APISession.swift */; }; 50D61E5D2AA4904F00A926EC /* FeedTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D61E5C2AA4904F00A926EC /* FeedTracking.swift */; }; 50DBB8E02A805836002870B1 /* MockErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DBB8DF2A805836002870B1 /* MockErrorHandler.swift */; }; - 50DBB8E22A80F9E4002870B1 /* APICommunity+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DBB8E12A80F9E4002870B1 /* APICommunity+Mock.swift */; }; 50EC39B22A346DDC00E014C2 /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EC39B12A346DDC00E014C2 /* URLHandler.swift */; }; 50F2851C2A5C5C1500CF8865 /* TokenRefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F2851B2A5C5C1500CF8865 /* TokenRefreshView.swift */; }; 50F830F82A4C92BF00D67099 /* FeedTrackerItemProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F830F72A4C92BF00D67099 /* FeedTrackerItemProviding.swift */; }; @@ -477,6 +478,8 @@ 503422572AAB798600EFE88D /* AppFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFlow.swift; sourceTree = ""; }; 503BA26E2A2C94540052516C /* URL+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Identifiable.swift"; sourceTree = ""; }; 504106CC2A744D7F000AAEF8 /* CommentRepository+Dependency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CommentRepository+Dependency.swift"; sourceTree = ""; }; + 504ECBAD2AB45B2A006C0B96 /* LemmyURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LemmyURL.swift; sourceTree = ""; }; + 504ECBB02AB4B101006C0B96 /* LemmyURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LemmyURLTests.swift; sourceTree = ""; }; 505240E22A86916500EA4558 /* FavoriteCommunitiesTracker+Dependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteCommunitiesTracker+Dependency.swift"; sourceTree = ""; }; 505240E42A86E32700EA4558 /* CommunityListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityListModel.swift; sourceTree = ""; }; 505240E62A88D36D00EA4558 /* SectionIndexTitles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionIndexTitles.swift; sourceTree = ""; }; @@ -530,7 +533,6 @@ 50D61E5A2AA32B9400A926EC /* APISession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISession.swift; sourceTree = ""; }; 50D61E5C2AA4904F00A926EC /* FeedTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedTracking.swift; sourceTree = ""; }; 50DBB8DF2A805836002870B1 /* MockErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockErrorHandler.swift; sourceTree = ""; }; - 50DBB8E12A80F9E4002870B1 /* APICommunity+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APICommunity+Mock.swift"; sourceTree = ""; }; 50EC39B12A346DDC00E014C2 /* URLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHandler.swift; sourceTree = ""; }; 50F2851B2A5C5C1500CF8865 /* TokenRefreshView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRefreshView.swift; sourceTree = ""; }; 50F830F72A4C92BF00D67099 /* FeedTrackerItemProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedTrackerItemProviding.swift; sourceTree = ""; }; @@ -1046,6 +1048,14 @@ path = TabBar; sourceTree = ""; }; + 504ECBAF2AB4B0DF006C0B96 /* Model */ = { + isa = PBXGroup; + children = ( + 504ECBB02AB4B101006C0B96 /* LemmyURLTests.swift */, + ); + path = Model; + sourceTree = ""; + }; 5064D03B2A6DE05000B22EE3 /* Notifications */ = { isa = PBXGroup; children = ( @@ -1163,7 +1173,6 @@ isa = PBXGroup; children = ( 50DBB8DF2A805836002870B1 /* MockErrorHandler.swift */, - 50DBB8E12A80F9E4002870B1 /* APICommunity+Mock.swift */, ); path = Mocks; sourceTree = ""; @@ -1460,6 +1469,7 @@ 6363D5D927EE196A00E34822 /* MlemTests */ = { isa = PBXGroup; children = ( + 504ECBAF2AB4B0DF006C0B96 /* Model */, 50CC4A802AA0D5F90074C845 /* Parsers */, 50CC4A7B2A9CFF840074C845 /* Supporting Files */, 50BC1AB72A89741000E3C48B /* Community List */, @@ -1526,6 +1536,7 @@ isa = PBXGroup; children = ( 637218032A3A2AAD008C4816 /* HierarchicalComment.swift */, + 504ECBAD2AB45B2A006C0B96 /* LemmyURL.swift */, ); path = Internal; sourceTree = ""; @@ -2534,6 +2545,7 @@ 6D405B032A43E7DB00C65F9C /* Sidebar Header Label.swift in Sources */, 50785F762A9A684300117245 /* SavedAccountTracker+Dependency.swift in Sources */, 632578182A29F83C00446A66 /* PostSortMenu.swift in Sources */, + 504ECBAE2AB45B2A006C0B96 /* LemmyURL.swift in Sources */, CDA217EA2A63093E00BDA173 /* ReportComment.swift in Sources */, CDA217E82A63029B00BDA173 /* ReportMention.swift in Sources */, 508845CF2A3641160088E483 /* JSONDecoder+Default.swift in Sources */, @@ -2808,11 +2820,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 504ECBB12AB4B101006C0B96 /* LemmyURLTests.swift in Sources */, 50DBB8E02A805836002870B1 /* MockErrorHandler.swift in Sources */, 6363D5DB27EE196A00E34822 /* MlemTests.swift in Sources */, 50BC1AB92A89744200E3C48B /* CommunityListModelTests.swift in Sources */, 50CC4A782A9CBDF70074C845 /* TimestampedValueTests.swift in Sources */, - 50DBB8E22A80F9E4002870B1 /* APICommunity+Mock.swift in Sources */, 50CC4A822AA0D61F0074C845 /* InstanceMetadataParserTests.swift in Sources */, 50C86ABC2A7E50E200277519 /* PersistenceRepositoryTests.swift in Sources */, ); diff --git a/Mlem/API/Internal/LemmyURL.swift b/Mlem/API/Internal/LemmyURL.swift new file mode 100644 index 000000000..632641339 --- /dev/null +++ b/Mlem/API/Internal/LemmyURL.swift @@ -0,0 +1,27 @@ +// +// LemmyURL.swift +// Mlem +// +// Created by mormaer on 15/09/2023. +// +// + +import Foundation + +struct LemmyURL { + let url: URL + + init?(string: String?) { + guard let string else { + return nil + } + + if let url = URL(string: string) { + self.url = url + } else if let encoded = string.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), let url = URL(string: encoded) { + self.url = url + } else { + return nil + } + } +} diff --git a/Mlem/API/Models/Community/APICommunity.swift b/Mlem/API/Models/Community/APICommunity.swift index b683277cb..f76d26266 100644 --- a/Mlem/API/Models/Community/APICommunity.swift +++ b/Mlem/API/Models/Community/APICommunity.swift @@ -20,8 +20,8 @@ struct APICommunity: Codable, Identifiable { let nsfw: Bool let actorId: URL let local: Bool - let icon: URL? - let banner: URL? + let icon: String? + let banner: String? let hidden: Bool let postingRestrictedToMods: Bool let instanceId: Int @@ -44,3 +44,8 @@ extension APICommunity: Comparable { return lhsFullCommunity < rhsFullCommunity } } + +extension APICommunity { + var iconUrl: URL? { LemmyURL(string: icon)?.url } + var bannerUrl: URL? { LemmyURL(string: banner)?.url } +} diff --git a/Mlem/API/Models/Person/APIPerson.swift b/Mlem/API/Models/Person/APIPerson.swift index a61b0f4ef..723a32800 100644 --- a/Mlem/API/Models/Person/APIPerson.swift +++ b/Mlem/API/Models/Person/APIPerson.swift @@ -12,16 +12,16 @@ struct APIPerson: Decodable, Identifiable, Hashable { let id: Int let name: String let displayName: String? - let avatar: URL? + let avatar: String? let banned: Bool let published: Date let updated: Date? let actorId: URL let bio: String? let local: Bool - let banner: URL? + let banner: String? let deleted: Bool - let sharedInboxUrl: URL? + let sharedInboxUrl: String? let matrixUserId: String? let admin: Bool? // this is no longer returned on beehaw... let botAccount: Bool @@ -34,3 +34,9 @@ extension APIPerson: Equatable { lhs.actorId == rhs.actorId } } + +extension APIPerson { + var avatarUrl: URL? { LemmyURL(string: avatar)?.url } + var bannerUrl: URL? { LemmyURL(string: banner)?.url } + var sharedInboxLink: URL? { LemmyURL(string: sharedInboxUrl)?.url } +} diff --git a/Mlem/API/Models/Posts/APIPost.swift b/Mlem/API/Models/Posts/APIPost.swift index f48c0b5ed..f520ea20b 100644 --- a/Mlem/API/Models/Posts/APIPost.swift +++ b/Mlem/API/Models/Posts/APIPost.swift @@ -11,7 +11,7 @@ import Foundation struct APIPost: Decodable { let id: Int let name: String - let url: URL? + let url: String? let body: String? let creatorId: Int let communityId: Int @@ -28,10 +28,15 @@ struct APIPost: Decodable { let nsfw: Bool let published: Date let removed: Bool - let thumbnailUrl: URL? + let thumbnailUrl: String? let updated: Date? } +extension APIPost { + var linkUrl: URL? { LemmyURL(string: url)?.url } + var thumbnailImageUrl: URL? { LemmyURL(string: thumbnailUrl)?.url } +} + extension APIPost: Equatable { static func == (lhs: APIPost, rhs: APIPost) -> Bool { lhs.id == rhs.id diff --git a/Mlem/API/Models/Posts/APIPostReport.swift b/Mlem/API/Models/Posts/APIPostReport.swift index a72792888..5a15e0392 100644 --- a/Mlem/API/Models/Posts/APIPostReport.swift +++ b/Mlem/API/Models/Posts/APIPostReport.swift @@ -13,7 +13,7 @@ struct APIPostReport: Decodable { let creatorId: Int let postId: Int let originalPostName: String - let originalPostUrl: URL? + let originalPostUrl: String? let originalPostBody: String? let reason: String let resolved: Bool @@ -21,3 +21,7 @@ struct APIPostReport: Decodable { let published: Date let updated: Date? } + +extension APIPostReport { + var originalUrl: URL? { LemmyURL(string: originalPostUrl)?.url } +} diff --git a/Mlem/API/Models/Site/APISite.swift b/Mlem/API/Models/Site/APISite.swift index 20dbc31b2..048be2b14 100644 --- a/Mlem/API/Models/Site/APISite.swift +++ b/Mlem/API/Models/Site/APISite.swift @@ -13,8 +13,8 @@ struct APISite: Decodable { let name: String let sidebar: String? let published: Date - let icon: URL? - let banner: URL? + let icon: String? + let banner: String? let description: String? let actorId: String? let lastRefreshedAt: Date @@ -22,3 +22,8 @@ struct APISite: Decodable { let publicKey: String let instanceId: Int } + +extension APISite { + var iconUrl: URL? { LemmyURL(string: icon)?.url } + var bannerUrl: URL? { LemmyURL(string: banner)?.url } +} diff --git a/Mlem/Extensions/Mocks/APICommunity+Mock.swift b/Mlem/Extensions/Mocks/APICommunity+Mock.swift index 400303e05..09254c80f 100644 --- a/Mlem/Extensions/Mocks/APICommunity+Mock.swift +++ b/Mlem/Extensions/Mocks/APICommunity+Mock.swift @@ -21,8 +21,8 @@ extension APICommunity { nsfw: Bool = false, actorId: URL = .mock, local: Bool = false, - icon: URL? = nil, - banner: URL? = nil, + icon: String? = nil, + banner: String? = nil, hidden: Bool = false, postingRestrictedToMods: Bool = false, instanceId: Int = 0 diff --git a/Mlem/Extensions/Mocks/APIPerson+Mock.swift b/Mlem/Extensions/Mocks/APIPerson+Mock.swift index 33eb4c4c4..c4fdbffff 100644 --- a/Mlem/Extensions/Mocks/APIPerson+Mock.swift +++ b/Mlem/Extensions/Mocks/APIPerson+Mock.swift @@ -13,16 +13,16 @@ extension APIPerson { id: Int = 0, name: String = "Mock Person", displayName: String? = nil, - avatar: URL? = nil, + avatar: String? = nil, banned: Bool = false, published: Date = .mock, updated: Date? = nil, actorId: URL = .mock, bio: String? = nil, local: Bool = false, - banner: URL? = nil, + banner: String? = nil, deleted: Bool = false, - sharedInboxUrl: URL? = nil, + sharedInboxUrl: String? = nil, matrixUserId: String? = nil, admin: Bool = false, botAccount: Bool = false, diff --git a/Mlem/Extensions/Mocks/APIPost+Mock.swift b/Mlem/Extensions/Mocks/APIPost+Mock.swift index d3269ec41..418d961d1 100644 --- a/Mlem/Extensions/Mocks/APIPost+Mock.swift +++ b/Mlem/Extensions/Mocks/APIPost+Mock.swift @@ -12,7 +12,7 @@ extension APIPost { static func mock( id: Int = 0, name: String = "Mock Post", - url: URL? = nil, + url: String? = nil, body: String? = nil, creatorId: Int = 0, communityId: Int = 0, @@ -29,7 +29,7 @@ extension APIPost { nsfw: Bool = false, published: Date = .mock, removed: Bool = false, - thumbnailUrl: URL? = nil, + thumbnailUrl: String? = nil, updated: Date? = nil ) -> APIPost { .init( diff --git a/Mlem/Extensions/Mocks/APISite+Mock.swift b/Mlem/Extensions/Mocks/APISite+Mock.swift index dc124753d..81e8cb5a9 100644 --- a/Mlem/Extensions/Mocks/APISite+Mock.swift +++ b/Mlem/Extensions/Mocks/APISite+Mock.swift @@ -14,8 +14,8 @@ extension APISite { name: String = "Mock Site", sidebar: String? = nil, published: Date = .mock, - icon: URL? = nil, - banner: URL? = nil, + icon: String? = nil, + banner: String? = nil, description: String? = nil, actorId: String? = nil, lastRefreshedAt: Date = .mock, diff --git a/Mlem/Models/Content/Post Model.swift b/Mlem/Models/Content/Post Model.swift index 1859f5375..b9cb4300d 100644 --- a/Mlem/Models/Content/Post Model.swift +++ b/Mlem/Models/Content/Post Model.swift @@ -73,9 +73,9 @@ struct PostModel { var postType: PostType { // post with URL: either image or link - if let postUrl = post.url { + if let postUrl = post.linkUrl { // if image, return image link, otherwise return thumbnail - return postUrl.isImage ? .image(postUrl) : .link(post.thumbnailUrl) + return postUrl.isImage ? .image(postUrl) : .link(post.thumbnailImageUrl) } // otherwise text, but post.body needs to be present, even if it's an empty string diff --git a/Mlem/Models/Trackers/Post Tracker.swift b/Mlem/Models/Trackers/Post Tracker.swift index 1bef4ff46..cf5f16a42 100644 --- a/Mlem/Models/Trackers/Post Tracker.swift +++ b/Mlem/Models/Trackers/Post Tracker.swift @@ -416,11 +416,11 @@ class PostTracker: ObservableObject { for post in newPosts { // preload user and community avatars--fetching both because we don't know which we'll need, but these are super tiny // so it's probably not an API crime, right? - if let communityAvatarLink = post.community.icon { + if let communityAvatarLink = post.community.iconUrl { imageRequests.append(ImageRequest(url: communityAvatarLink.withIcon64Parameters)) } - if let userAvatarLink = post.creator.avatar { + if let userAvatarLink = post.creator.avatarUrl { imageRequests.append(ImageRequest(url: userAvatarLink.withIcon64Parameters)) } @@ -430,7 +430,7 @@ class PostTracker: ObservableObject { imageRequests.append(ImageRequest(url: url, priority: .high)) case let .link(url): // websites: load image and favicon - if let baseURL = post.post.url?.host, + if let baseURL = post.post.linkUrl?.host, let favIconURL = URL(string: "https://www.google.com/s2/favicons?sz=64&domain=\(baseURL)") { imageRequests.append(ImageRequest(url: favIconURL)) } diff --git a/Mlem/Views/Shared/Accounts/Add Account View.swift b/Mlem/Views/Shared/Accounts/Add Account View.swift index 60f393761..9d4e7862a 100644 --- a/Mlem/Views/Shared/Accounts/Add Account View.swift +++ b/Mlem/Views/Shared/Accounts/Add Account View.swift @@ -320,7 +320,7 @@ struct AddSavedInstanceView: View { instanceLink: instanceURL, accessToken: response.jwt, username: username, - avatarUrl: user.avatar + avatarUrl: user.avatarUrl ) // MARK: - Save the account's credentials into the keychain diff --git a/Mlem/Views/Shared/Accounts/Components/Instance Summary.swift b/Mlem/Views/Shared/Accounts/Components/Instance Summary.swift index 8eb12c6a7..3eb6ae5c7 100644 --- a/Mlem/Views/Shared/Accounts/Components/Instance Summary.swift +++ b/Mlem/Views/Shared/Accounts/Components/Instance Summary.swift @@ -52,7 +52,7 @@ struct InstanceSummary: View { if let siteData { VStack(spacing: AppConstants.postAndCommentSpacing) { HStack(alignment: .top, spacing: AppConstants.postAndCommentSpacing) { - instanceIcon(url: siteData.site.icon) + instanceIcon(url: siteData.site.iconUrl) .padding(.leading, 1) Spacer() diff --git a/Mlem/Views/Shared/Components/Thumbnail Image View.swift b/Mlem/Views/Shared/Components/Thumbnail Image View.swift index beeca92c6..df4646648 100644 --- a/Mlem/Views/Shared/Components/Thumbnail Image View.swift +++ b/Mlem/Views/Shared/Components/Thumbnail Image View.swift @@ -43,7 +43,7 @@ struct ThumbnailImageView: View { contentMode: .fill ) .onTapGesture { - if let url = post.post.url { + if let url = post.post.linkUrl { openURL(url) markPostAsRead() } diff --git a/Mlem/Views/Shared/Composer/PostComposerView.swift b/Mlem/Views/Shared/Composer/PostComposerView.swift index bdc78c2ce..363f70a04 100644 --- a/Mlem/Views/Shared/Composer/PostComposerView.swift +++ b/Mlem/Views/Shared/Composer/PostComposerView.swift @@ -27,7 +27,7 @@ struct PostComposerView: View { self.editModel = editModel self._postTitle = State(initialValue: editModel.editPost?.post.name ?? "") - self._postURL = State(initialValue: editModel.editPost?.post.url?.description ?? "") + self._postURL = State(initialValue: editModel.editPost?.post.linkUrl?.description ?? "") self._postBody = State(initialValue: editModel.editPost?.post.body ?? "") self._isNSFW = State(initialValue: editModel.editPost?.post.nsfw ?? false) } diff --git a/Mlem/Views/Shared/Links/AvatarView.swift b/Mlem/Views/Shared/Links/AvatarView.swift index 3ee58a6f1..eca915d2d 100644 --- a/Mlem/Views/Shared/Links/AvatarView.swift +++ b/Mlem/Views/Shared/Links/AvatarView.swift @@ -20,21 +20,21 @@ struct AvatarView: View { let blurAvatar: Bool init(community: APICommunity, avatarSize: CGFloat, lineColor: Color? = nil) { - @AppStorage("shouldBlurNsfw") var shouldBlurNsfw: Bool = true + @AppStorage("shouldBlurNsfw") var shouldBlurNsfw = true self.type = .community - self.url = community.icon + self.url = community.iconUrl self.avatarSize = avatarSize self.lineColor = lineColor ?? Color(UIColor.secondarySystemBackground) - self.clipAvatar = AvatarView.shouldClipCommunityAvatar(url: community.icon) + self.clipAvatar = AvatarView.shouldClipCommunityAvatar(url: community.iconUrl) self.blurAvatar = shouldBlurNsfw && community.nsfw } init(user: APIPerson, avatarSize: CGFloat, blurAvatar: Bool = false, lineColor: Color? = nil) { - @AppStorage("shouldBlurNsfw") var shouldBlurNsfw: Bool = true + @AppStorage("shouldBlurNsfw") var shouldBlurNsfw = true self.type = .user - self.url = user.avatar + self.url = user.avatarUrl self.avatarSize = avatarSize self.lineColor = lineColor ?? Color(UIColor.secondarySystemBackground) self.clipAvatar = false @@ -51,7 +51,7 @@ struct AvatarView: View { var body: some View { Group { - if let url = url { + if let url { CachedImage( url: url.withIcon64Parameters, shouldExpand: false, @@ -93,8 +93,8 @@ struct AvatarView: View { .frame(width: avatarSize * 0.83, height: avatarSize * 0.83) ) } - .frame(maxWidth: .infinity) - .background(.gray) + .frame(maxWidth: .infinity) + .background(.gray) ) case .user: return AnyView( diff --git a/Mlem/Views/Shared/Website Icon Complex.swift b/Mlem/Views/Shared/Website Icon Complex.swift index 0cd33b00c..19b18b1ed 100644 --- a/Mlem/Views/Shared/Website Icon Complex.swift +++ b/Mlem/Views/Shared/Website Icon Complex.swift @@ -32,7 +32,7 @@ struct WebsiteIconComplex: View { var faviconURL: URL? { guard - let baseURL = post.url?.host, + let baseURL = post.linkUrl?.host, let imageURL = URL(string: "https://www.google.com/s2/favicons?sz=64&domain=\(baseURL)") else { return nil @@ -50,7 +50,7 @@ struct WebsiteIconComplex: View { } var linkHost: String { - if let url = post.url { + if let url = post.linkUrl { return url.host ?? "some website" } return "some website" @@ -58,7 +58,7 @@ struct WebsiteIconComplex: View { var body: some View { VStack(spacing: 0) { - if shouldShowWebsitePreviews, let thumbnailURL = post.thumbnailUrl { + if shouldShowWebsitePreviews, let thumbnailURL = post.thumbnailImageUrl { CachedImage(url: thumbnailURL, shouldExpand: false) .frame(maxHeight: 400) .applyNsfwOverlay(post.nsfw) @@ -101,7 +101,7 @@ struct WebsiteIconComplex: View { ) .contentShape(Rectangle()) .onTapGesture { - if let url = post.url { + if let url = post.linkUrl { openURL(url) if let onTapActions { onTapActions() diff --git a/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift b/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift index f624ce7e5..b96cd98d8 100644 --- a/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift +++ b/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift @@ -67,8 +67,8 @@ struct CommunitySidebarView: View { title: communityDetails.communityView.community.name, subtitle: "@\(communityDetails.communityView.community.name)@\(communityDetails.communityView.community.actorId.host()!)", avatarSubtext: .constant("Created \(getRelativeTime(date: communityDetails.communityView.community.published))"), - bannerURL: shouldShowCommunityHeaders ? communityDetails.communityView.community.banner : nil, - avatarUrl: communityDetails.communityView.community.icon, + bannerURL: shouldShowCommunityHeaders ? communityDetails.communityView.community.bannerUrl : nil, + avatarUrl: communityDetails.communityView.community.iconUrl, label1: "\(communityDetails.communityView.counts.subscribers) Subscribers" ) @@ -139,8 +139,8 @@ struct SidebarPreview: PreviewProvider { title: "Test Community", description: previewCommunityDescription, actorId: URL(string: "https://lemmy.foo.com/c/testcommunity")!, - icon: URL(string: "https://vlemmy.net/pictrs/image/190f2d6a-ac38-448d-ae9b-f6d751eb6e69.png?format=webp"), - banner: URL(string: "https://vlemmy.net/pictrs/image/719b61b3-8d8e-4aec-9f15-17be4a081f97.jpeg?format=webp") + icon: "https://vlemmy.net/pictrs/image/190f2d6a-ac38-448d-ae9b-f6d751eb6e69.png?format=webp", + banner: "https://vlemmy.net/pictrs/image/719b61b3-8d8e-4aec-9f15-17be4a081f97.jpeg?format=webp" ) static let previewUser: APIPerson = .mock( diff --git a/Mlem/Views/Tabs/Profile/User View.swift b/Mlem/Views/Tabs/Profile/User View.swift index c195d7ce5..5d7013bf8 100644 --- a/Mlem/Views/Tabs/Profile/User View.swift +++ b/Mlem/Views/Tabs/Profile/User View.swift @@ -101,8 +101,8 @@ struct UserView: View { subtitle: "@\(userDetails.person.name)@\(userDetails.person.actorId.host()!)", avatarSubtext: $avatarSubtext, avatarSubtextClicked: toggleCakeDayVisible, - bannerURL: shouldShowUserHeaders ? userDetails.person.banner : nil, - avatarUrl: userDetails.person.avatar, + bannerURL: shouldShowUserHeaders ? userDetails.person.bannerUrl : nil, + avatarUrl: userDetails.person.avatarUrl, label1: "\(userDetails.counts.commentCount) Comments", label2: "\(userDetails.counts.postCount) Posts" ) @@ -245,7 +245,7 @@ struct UserView: View { // take this opportunity to update the users avatar url to catch changes // we should be able to shift this down to the repository layer in the future so that we // catch anytime the app loads the signed in users details from any location in the app 🤞 - let url = response.personView.person.avatar + let url = response.personView.person.avatarUrl let updatedAccount = SavedAccount( id: currentAccount.id, instanceLink: currentAccount.instanceLink, @@ -290,11 +290,11 @@ struct UserViewPreview: PreviewProvider { id: name.hashValue, name: name, displayName: displayName, - avatar: URL(string: "https://lemmy.ml/pictrs/image/df86c06d-341c-4e79-9c80-d7c7eb64967a.jpeg?format=webp"), + avatar: "https://lemmy.ml/pictrs/image/df86c06d-341c-4e79-9c80-d7c7eb64967a.jpeg?format=webp", published: Date.now.advanced(by: -10000), actorId: URL(string: "https://google.com")!, bio: "Just here for the good vibes!", - banner: URL(string: "https://i.imgur.com/wcayaCB.jpeg"), + banner: "https://i.imgur.com/wcayaCB.jpeg", admin: userType == .admin, botAccount: userType == .bot ) diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift index 4239fba9e..d32ab15ce 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift @@ -134,7 +134,7 @@ struct PostSettingsView: View { APIPost( id: 0, name: "", - url: URL(string: "https://lemmy.ml/post/1011734")!, + url: "https://lemmy.ml/post/1011734", body: "", creatorId: 0, communityId: 0, @@ -151,7 +151,7 @@ struct PostSettingsView: View { nsfw: false, published: .now, removed: false, - thumbnailUrl: URL(string: "https://lemmy.ml/pictrs/image/1b759945-6651-497c-bee0-9bdb68f4a829.png"), + thumbnailUrl: "https://lemmy.ml/pictrs/image/1b759945-6651-497c-bee0-9bdb68f4a829.png", updated: nil ) ) diff --git a/MlemTests/Mocks/APICommunity+Mock.swift b/MlemTests/Mocks/APICommunity+Mock.swift deleted file mode 100644 index 74080c9b4..000000000 --- a/MlemTests/Mocks/APICommunity+Mock.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// APICommunity+Mock.swift -// MlemTests -// -// Created by mormaer on 07/08/2023. -// -// - -@testable import Mlem - -import Foundation - -extension APICommunity { - static func mock( - id: Int = 0, - name: String = "Mock Community", - title: String = "Mock", - description: String? = nil, - published: Date = .now, - updated: Date? = nil, - removed: Bool = false, - deleted: Bool = false, - nsfw: Bool = false, - actorId: URL = URL(string: "https://mlem.group")!, - local: Bool = true, - icon: URL? = nil, - banner: URL? = nil, - hidden: Bool = false, - postingRestrictedToMods: Bool = false, - instanceId: Int = 0 - ) -> Self { - self.init( - id: id, - name: name, - title: title, - description: description, - published: published, - updated: updated, - removed: removed, - deleted: deleted, - nsfw: nsfw, - actorId: actorId, - local: local, - icon: icon, - banner: banner, - hidden: hidden, - postingRestrictedToMods: postingRestrictedToMods, - instanceId: instanceId - ) - } -} diff --git a/MlemTests/Model/LemmyURLTests.swift b/MlemTests/Model/LemmyURLTests.swift new file mode 100644 index 000000000..864597df5 --- /dev/null +++ b/MlemTests/Model/LemmyURLTests.swift @@ -0,0 +1,33 @@ +// +// LemmyURLTests.swift +// MlemTests +// +// Created by mormaer on 15/09/2023. +// +// + +@testable import Mlem +import XCTest + +final class LemmyURLTests: XCTestCase { + func testHandlesValidURL() throws { + let validUrl = "https://mlem.group" + let lemmyUrl = LemmyURL(string: validUrl) + // expectation is the URL will be unchanged as it's already valid + XCTAssertEqual(lemmyUrl?.url.absoluteString, validUrl) + } + + func testHandlesUnencodedURL() throws { + let unencodedUrl = "https://matrix.to/#/#space:lemmy.world" + let lemmyUrl = LemmyURL(string: unencodedUrl) + // expectation is that the # character will be encoded to %23 + XCTAssertEqual(lemmyUrl?.url.absoluteString, "https://matrix.to/%23/%23space:lemmy.world") + } + + func testHandlesEncodedURL() throws { + let encodedUrl = "https://matrix.to/%23/%23space:lemmy.world" + let lemmyUrl = LemmyURL(string: encodedUrl) + // expectation is that the URL will be unchanged as it's already valid/encoded + XCTAssertEqual(lemmyUrl?.url.absoluteString, "https://matrix.to/%23/%23space:lemmy.world") + } +}