From 2e4d605eccb2b8799d2913b1214130b3de1b4183 Mon Sep 17 00:00:00 2001 From: Wilhelm Oks Date: Wed, 11 Dec 2024 11:54:12 +0100 Subject: [PATCH] Models WIP: AttachedImage, Link, Rant.Weekly, User.Avatar --- .../SwiftDevRant/Models/AttachedImage.swift | 30 ++++++++++ .../SwiftDevRant/Models/Collaboration.swift | 8 +++ Sources/SwiftDevRant/Models/Link.swift | 58 +++++++++++++++++++ Sources/SwiftDevRant/Models/Rant.Weekly.swift | 36 ++++++++++++ Sources/SwiftDevRant/Models/Rant.swift | 40 +++++++------ Sources/SwiftDevRant/Models/User.Avatar.swift | 27 +++++++++ Sources/SwiftDevRant/Models/User.swift | 8 +-- 7 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 Sources/SwiftDevRant/Models/AttachedImage.swift create mode 100644 Sources/SwiftDevRant/Models/Link.swift create mode 100644 Sources/SwiftDevRant/Models/Rant.Weekly.swift create mode 100644 Sources/SwiftDevRant/Models/User.Avatar.swift diff --git a/Sources/SwiftDevRant/Models/AttachedImage.swift b/Sources/SwiftDevRant/Models/AttachedImage.swift new file mode 100644 index 0000000..80aa375 --- /dev/null +++ b/Sources/SwiftDevRant/Models/AttachedImage.swift @@ -0,0 +1,30 @@ +/// An image that the user has uploaded for his rant or comment. +public struct AttachedImage: Hashable { + public let url: String + public let width: Int + public let height: Int + + public init(url: String, width: Int, height: Int) { + self.url = url + self.width = width + self.height = height + } +} + +extension AttachedImage { + struct CodingData: Codable { + let url: String + let width: Int + let height: Int + } +} + +extension AttachedImage.CodingData { + var decoded: AttachedImage { + .init( + url: url, + width: width, + height: height + ) + } +} diff --git a/Sources/SwiftDevRant/Models/Collaboration.swift b/Sources/SwiftDevRant/Models/Collaboration.swift index b64ed1d..af35f30 100644 --- a/Sources/SwiftDevRant/Models/Collaboration.swift +++ b/Sources/SwiftDevRant/Models/Collaboration.swift @@ -4,4 +4,12 @@ public struct Collaboration: Hashable { public let techStack: String public let teamSize: String public let url: String + + public init(type: String, description: String, techStack: String, teamSize: String, url: String) { + self.type = type + self.description = description + self.techStack = techStack + self.teamSize = teamSize + self.url = url + } } diff --git a/Sources/SwiftDevRant/Models/Link.swift b/Sources/SwiftDevRant/Models/Link.swift new file mode 100644 index 0000000..4c5a648 --- /dev/null +++ b/Sources/SwiftDevRant/Models/Link.swift @@ -0,0 +1,58 @@ +/// A URL or a user mention link in a rant or comment. +public struct Link: Hashable { + public enum Kind { + case url + case userMention + } + + public let kind: Kind + + /// The full URL. + public let url: String + + public let shortURL: String? //TODO: what is this and what is it used for? + + /// The url as it is visible in the text of the rant or comment. + public let title: String + + /// The starting position of the link in the overall text of the rant or comment. + /// - Important: The devRant API returns offsets for links in byte offsets and not in normalized character offsets. Please take this into account when using these offsets. + public let start: Int? + + /// The ending position of the link in the overall text of the rant or comment. + /// - Important: The devRant API returns offsets for links in byte offsets and not in normalized character offsets. Please take this into account when using these offsets. + public let end: Int? + + public init(kind: Link.Kind, url: String, shortURL: String?, title: String, start: Int?, end: Int?) { + self.kind = kind + self.url = url + self.shortURL = shortURL + self.title = title + self.start = start + self.end = end + } +} + +extension Link { + struct CodingData: Codable { + let type: String + let url: String + let short_url: String + let title: String + let start: Int? + let end: Int? + } +} + +extension Link.CodingData { + var decoded: Link { + .init( + kind: type == "mention" ? .userMention : .url, + url: url, + shortURL: short_url, + title: title, + start: start, + end: end + ) + } +} diff --git a/Sources/SwiftDevRant/Models/Rant.Weekly.swift b/Sources/SwiftDevRant/Models/Rant.Weekly.swift new file mode 100644 index 0000000..5c2a55f --- /dev/null +++ b/Sources/SwiftDevRant/Models/Rant.Weekly.swift @@ -0,0 +1,36 @@ +public extension Rant { + /// Holds information about a specific weekly group rant. + struct Weekly: Hashable { + public let week: Int + public let topic: String + public let date: String + public let uiHeight: Int + + public init(week: Int, topic: String, date: String, uiHeight: Int) { + self.week = week + self.topic = topic + self.date = date + self.uiHeight = uiHeight + } + } +} + +extension Rant.Weekly { + struct CodingData: Codable { + let week: Int + let topic: String + let date: String + let height: Int + } +} + +extension Rant.Weekly.CodingData { + var decoded: Rant.Weekly { + .init( + week: week, + topic: topic, + date: date, + uiHeight: height + ) + } +} diff --git a/Sources/SwiftDevRant/Models/Rant.swift b/Sources/SwiftDevRant/Models/Rant.swift index 51151c4..c64ea45 100644 --- a/Sources/SwiftDevRant/Models/Rant.swift +++ b/Sources/SwiftDevRant/Models/Rant.swift @@ -1,8 +1,6 @@ import Foundation public struct Rant: Identifiable, Hashable { - //TODO: public let weekly: Weekly? - /// The id of this rant. public let id: Int @@ -15,8 +13,8 @@ public struct Rant: Identifiable, Hashable { /// The time when this rant was created. public let created: Date - /// If the rant has an image attached to it, a URL of the image will be stored in this. - //TODO: public let attachedImage: AttachedImage? + /// The optional image that the user has uploaded for this rant. + public let image: AttachedImage? /// The number of comments that this rant has. public let numberOfComments: Int @@ -33,17 +31,20 @@ public struct Rant: Identifiable, Hashable { /// True if this rant has been marked as a favorite by the logged in user. public var isFavorite: Bool - /// A url link to this rant. - public let link: String? + /// A URL link to this rant. + public let linkToRant: String? - /// If the rant includes URLs in the text, those that were successfully parsed by the server will be in this array. - //TODO: public var links: [Link]? + /// The URLs and user mentions inside of the text of this rant. + public var linksInText: [Link] + + /// Holds information about the weekly topic if this rant is of type weekly. + public let weekly: Weekly? /// Holds information about the collaboration project if this rant is of type collaboration. - let collaboration: Collaboration? + public let collaboration: Collaboration? /// The user who wrote this rant. - let author: User + public let author: User } extension Rant { @@ -52,15 +53,15 @@ extension Rant { let text: String let score: Int let created_time: Int - //TODO: let attachedImage: AttachedImage? + let attached_image: AttachedImage.CodingData? let num_comments: Int let tags: [String] let vote_state: Int - //TODO: let weekly: Weekly? let edited: Bool let favorited: Int? let link: String? - //TODO: let links: [Link] + let links: [Link.CodingData]? + let weekly: Weekly.CodingData? let c_type_long: String? let c_description: String? let c_tech_stack: String? @@ -69,8 +70,8 @@ extension Rant { let user_id: Int let user_username: String let user_score: Int - //TODO: let user_avatar: UserAvatar - //TODO: let user_avatar_lg: UserAvatar + let user_avatar: User.Avatar.CodingData + let user_avatar_lg: User.Avatar.CodingData let user_dpp: Int? } } @@ -82,18 +83,23 @@ extension Rant.CodingData { text: text, score: score, created: Date(timeIntervalSince1970: TimeInterval(created_time)), + image: attached_image?.decoded, numberOfComments: num_comments, tags: tags, voteState: .init(rawValue: vote_state) ?? .unvoted, isEdited: edited, isFavorite: (favorited ?? 0) != 0, - link: link, + linkToRant: link, + linksInText: links?.map(\.decoded) ?? [], + weekly: weekly?.decoded, collaboration: decodedCollaboration, author: .init( id: user_id, name: user_username, score: user_score, - devRantSupporter: (user_dpp ?? 0) != 0 + devRantSupporter: (user_dpp ?? 0) != 0, + avatar: user_avatar.decoded, + avatarLarge: user_avatar_lg.decoded ) ) } diff --git a/Sources/SwiftDevRant/Models/User.Avatar.swift b/Sources/SwiftDevRant/Models/User.Avatar.swift new file mode 100644 index 0000000..c4dd8a9 --- /dev/null +++ b/Sources/SwiftDevRant/Models/User.Avatar.swift @@ -0,0 +1,27 @@ +public extension User { + struct Avatar: Hashable { + public let colorHex: String + + public let imageUrlPath: String? + + public var imageUrl: String? { + imageUrlPath.flatMap { "https://avatars.devrant.com/\($0)" } + } + } +} + +extension User.Avatar { + struct CodingData: Codable { + let b: String + let i: String? + } +} + +extension User.Avatar.CodingData { + var decoded: User.Avatar { + .init( + colorHex: b, + imageUrlPath: i + ) + } +} diff --git a/Sources/SwiftDevRant/Models/User.swift b/Sources/SwiftDevRant/Models/User.swift index 2b3568e..2729399 100644 --- a/Sources/SwiftDevRant/Models/User.swift +++ b/Sources/SwiftDevRant/Models/User.swift @@ -4,9 +4,9 @@ public struct User: Identifiable, Hashable { public let score: Int public let devRantSupporter: Bool - /// The author's avatar, can be used optimally for small portraits of the user. - //TODO: public let userAvatar: UserAvatar + /// A small avatar for the rant views and comment views. + public let avatar: Avatar - /// A larger version of the author's avatar, can be used optimally for profile screens. - //TODO: public let userAvatarLarge: UserAvatar + /// A large avatar for the profile view. + public let avatarLarge: Avatar }