From 394e9a92c301842f2538c689e18e8b6df41e10b6 Mon Sep 17 00:00:00 2001 From: Wilhelm Oks Date: Thu, 12 Dec 2024 16:33:46 +0100 Subject: [PATCH] Models WIP: Notification, NotificationFeed --- .../SwiftDevRant/Models/Notification.swift | 86 +++++++++++++++++++ .../NotificationFeed.UnreadNumbers.swift | 50 +++++++++++ .../Models/NotificationFeed.UserInfo.swift | 49 +++++++++++ .../Models/NotificationFeed.swift | 51 +++++++++++ 4 files changed, 236 insertions(+) create mode 100644 Sources/SwiftDevRant/Models/Notification.swift create mode 100644 Sources/SwiftDevRant/Models/NotificationFeed.UnreadNumbers.swift create mode 100644 Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift create mode 100644 Sources/SwiftDevRant/Models/NotificationFeed.swift diff --git a/Sources/SwiftDevRant/Models/Notification.swift b/Sources/SwiftDevRant/Models/Notification.swift new file mode 100644 index 0000000..e5334aa --- /dev/null +++ b/Sources/SwiftDevRant/Models/Notification.swift @@ -0,0 +1,86 @@ +import Foundation + +/// A notification about activities in a rant or a comment. +public struct Notification: Hashable, Identifiable { + public enum Kind: String { + /// An upvote for a rant. + case rantUpvote = "content_vote" + + /// An upvote for a comment. + case commentUpvote = "comment_vote" + + /// A new comment in one of the logged in user's rants. + case newCommentInOwnRant = "comment_content" + + /// A new comment in a rant that the logged in user has commented in. + case newComment = "comment_discuss" + + /// A mention of the logged in user in a comment. + case mentionInComment = "comment_mention" + + /// A new rant posted by someone that the logged in user is subscribed to. + case newRantOfSubscribedUser = "rant_sub" + } + + /// The id of the rant associated with this notification. + public let rantId: Int + + /// The id of the comment associated with this notification, if this notification is for a comment. + public let commentId: Int? + + /// The time when this notification was created. + public let created: Date + + /// True if the user has already read this notification. + public let read: Bool + + /// The type of this notification. + public let kind: Kind + + /// The id of the user who triggered the notification. + public let userId: Int + + public var id: String { + [ + String(rantId), + commentId.flatMap{ String($0) } ?? "-", + String(Int(created.timeIntervalSince1970)), + String(read), + kind.rawValue, + String(userId) + ].joined(separator: "|") + } + + public init(rantId: Int, commentId: Int?, created: Date, read: Bool, kind: Notification.Kind, userId: Int) { + self.rantId = rantId + self.commentId = commentId + self.created = created + self.read = read + self.kind = kind + self.userId = userId + } +} + +extension Notification { + struct CodingData: Codable { + let rant_id: Int + let comment_id: Int? + let created_time: Int + let read: Int + let type: String + let uid: Int + } +} + +extension Notification.CodingData { + var decoded: Notification { + .init( + rantId: rant_id, + commentId: comment_id, + created: Date(timeIntervalSince1970: TimeInterval(created_time)), + read: read != 0, + kind: .init(rawValue: type) ?? .newComment, + userId: uid + ) + } +} diff --git a/Sources/SwiftDevRant/Models/NotificationFeed.UnreadNumbers.swift b/Sources/SwiftDevRant/Models/NotificationFeed.UnreadNumbers.swift new file mode 100644 index 0000000..bd4ac99 --- /dev/null +++ b/Sources/SwiftDevRant/Models/NotificationFeed.UnreadNumbers.swift @@ -0,0 +1,50 @@ +public extension NotificationFeed { + /// Holds numbers of unread notifications for each type of notification. + struct UnreadNumbers: Decodable, Hashable { + /// The total number of unread notifications + public let all: Int + + /// The number of unread commets. + public let comments: Int + + /// The number of unread mentions. + public let mentions: Int + + /// The number of unread rants from users which the logged in user is subscribed to. + public let subscriptions: Int + + /// The number of unread upvotes. + public let upvotes: Int + + public init(all: Int, comments: Int, mentions: Int, subscriptions: Int, upvotes: Int) { + self.all = all + self.comments = comments + self.mentions = mentions + self.subscriptions = subscriptions + self.upvotes = upvotes + } + } +} + +extension NotificationFeed.UnreadNumbers { + struct CodingData: Codable { + let all: Int + let comments: Int + let mentions: Int + let subs: Int + let upvotes: Int + //let total: Int //Not needed because it's the same as `all`. + } +} + +extension NotificationFeed.UnreadNumbers.CodingData { + var decoded: NotificationFeed.UnreadNumbers { + .init( + all: all, + comments: comments, + mentions: mentions, + subscriptions: subs, + upvotes: upvotes + ) + } +} diff --git a/Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift b/Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift new file mode 100644 index 0000000..d4eaf27 --- /dev/null +++ b/Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift @@ -0,0 +1,49 @@ +public extension NotificationFeed { + struct UserInfo: Hashable { + public let avatar: User.Avatar + public let username: String + public let userId: String //TODO: why is this String? The other user ids are Int. + + public init(avatar: User.Avatar, username: String, userId: String) { + self.avatar = avatar + self.username = username + self.userId = userId + } + } +} + +extension NotificationFeed.UserInfo { + struct CodingData: Decodable { + struct Container: Decodable { + let array: [CodingData] + } + + let avatar: User.Avatar.CodingData + let name: String + let uidForUsername: String //TODO: why is this String? The other user ids are Int. + + private enum CodingKeys: CodingKey { + case avatar + case name + } + + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + + avatar = try values.decode(User.Avatar.CodingData.self, forKey: .avatar) + name = try values.decode(String.self, forKey: .name) + + uidForUsername = values.codingPath[values.codingPath.endIndex - 1].stringValue //TODO: wtf is this? Check if it can be made simpler and easier to understand. + } + } +} + +extension NotificationFeed.UserInfo.CodingData { + var decoded: NotificationFeed.UserInfo { + .init( + avatar: avatar.decoded, + username: name, + userId: uidForUsername + ) + } +} diff --git a/Sources/SwiftDevRant/Models/NotificationFeed.swift b/Sources/SwiftDevRant/Models/NotificationFeed.swift new file mode 100644 index 0000000..2f681a4 --- /dev/null +++ b/Sources/SwiftDevRant/Models/NotificationFeed.swift @@ -0,0 +1,51 @@ +import Foundation + +/// Contains a list of all notifications for the logged in user and the numbers of unread notifications. +public struct NotificationFeed: Hashable { + public enum Categories: String, CaseIterable { + case all = "" + case upvotes = "upvotes" + case mentions = "mentions" + case comments = "comments" + case subscriptions = "subs" + } + + /// The time when the notifications were last checked. + public let lastChecked: Date + + /// The list of all notifications for the logged in user. + public let notifications: [Notification] + + /// The numbers of unread notifications. + public let unreadNumbers: UnreadNumbers + + /// Infos about the user name and avatar for each user id. + public let userInfos: [UserInfo] + + public init(lastChecked: Date, notifications: [Notification], unreadNumbers: NotificationFeed.UnreadNumbers, userInfos: [UserInfo]) { + self.lastChecked = lastChecked + self.notifications = notifications + self.unreadNumbers = unreadNumbers + self.userInfos = userInfos + } +} + +extension NotificationFeed { + struct CodingData: Decodable { + let check_time: Int + let items: [Notification.CodingData] + let unread: UnreadNumbers.CodingData + let username_map: UserInfo.CodingData.Container + } +} + +extension NotificationFeed.CodingData { + var decoded: NotificationFeed { + .init( + lastChecked: Date(timeIntervalSince1970: TimeInterval(check_time)), + notifications: items.map(\.decoded), + unreadNumbers: unread.decoded, + userInfos: username_map.array.map(\.decoded) + ) + } +}