Skip to content

Commit

Permalink
Merge pull request #64 from makinosp/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
makinosp authored Aug 2, 2024
2 parents 74c76cf + 22342d9 commit fd8281e
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// KeyedDecodingContainer+decodeSafeNullableArray.swift
// VRCKit
//
// Created by makinosp on 2024/08/02.
//

import Foundation

public extension KeyedDecodingContainer {
/// Decodes an array for the specified key and returns it. If the decoding fails, it returns an empty array.
///
/// This method attempts to decode a `SafeDecodingArray` from the container for the specified key. If the key is not
/// present or if the decoding fails, it returns an empty `SafeDecodingArray`.
///
/// - Parameters:
/// - type: The type of elements in the array to decode.
/// - key: The key that the decoded value is associated with.
/// - Returns: A `SafeDecodingArray` of the specified type.
/// - Throws: Rethrows any errors encountered during decoding.
func decodeSafeNullableArray<T>(
_ type: T.Type,
forKey key: KeyedDecodingContainer<K>.Key
) throws -> SafeDecodingArray<T> where T: Decodable {
try decodeIfPresent(SafeDecodingArray<T>.self, forKey: key) ?? SafeDecodingArray()
}
}
62 changes: 53 additions & 9 deletions Sources/VRCKit/Models/FriendModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,73 @@ import Foundation

extension Friend: ProfileElementRepresentable, LocationRepresentable {}

public struct Friend: Codable {
public struct Friend {
public let bio: String?
public let bioLinks: [String]?
public let currentAvatarImageUrl: String?
public let currentAvatarThumbnailImageUrl: String?
public var bioLinks: SafeDecodingArray<URL>
public let avatarImageUrl: URL?
public let avatarThumbnailUrl: URL?
public let displayName: String
public let id: String
public let isFriend: Bool
public let lastLogin: Date
public let lastPlatform: String
public let profilePicOverride: String?
public let profilePicOverride: URL?
public let status: UserStatus
public let statusDescription: String
public let tags: [String]
public let userIcon: String?
public let tags: [Tag]
public let userIcon: URL?
public let location: String
public let friendKey: String
}

extension FriendsLocation: LocationRepresentable {}
extension Friend: Codable {
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
bio = try container.decodeIfPresent(String.self, forKey: .bio)
bioLinks = try container.decodeSafeNullableArray(URL.self, forKey: .bioLinks)
avatarImageUrl = try container.decodeIfPresent(URL.self, forKey: .avatarImageUrl)
avatarThumbnailUrl = try container.decodeIfPresent(URL.self, forKey: .avatarThumbnailUrl)
displayName = try container.decode(String.self, forKey: .displayName)
id = try container.decode(String.self, forKey: .id)
isFriend = try container.decode(Bool.self, forKey: .isFriend)
lastLogin = try container.decode(Date.self, forKey: .lastLogin)
lastPlatform = try container.decode(String.self, forKey: .lastPlatform)
profilePicOverride = try? container.decodeIfPresent(URL.self, forKey: .profilePicOverride)
status = try container.decode(UserStatus.self, forKey: .status)
statusDescription = try container.decode(String.self, forKey: .statusDescription)
tags = try container.decode([Tag].self, forKey: .tags)
userIcon = try? container.decodeIfPresent(URL.self, forKey: .userIcon)
location = try container.decode(String.self, forKey: .location)
friendKey = try container.decode(String.self, forKey: .friendKey)
}
}

extension Friend {
private enum CodingKeys: String, CodingKey {
case bio
case bioLinks
case avatarImageUrl = "currentAvatarImageUrl"
case avatarThumbnailUrl = "currentAvatarThumbnailImageUrl"
case displayName
case id
case isFriend
case lastLogin
case lastPlatform
case profilePicOverride
case status
case statusDescription
case tags
case userIcon
case location
case friendKey
}
}

public struct FriendsLocation: Identifiable, Hashable {
public struct FriendsLocation: LocationRepresentable {
public let location: String
public let friends: [Friend]
}

extension FriendsLocation: Hashable, Identifiable {
public var id: String { location }
}
87 changes: 78 additions & 9 deletions Sources/VRCKit/Models/UserDetailModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,94 @@ import Foundation

extension UserDetail: ProfileDetailRepresentable, LocationRepresentable {}

public struct UserDetail: Codable {
public let bio: String?
public let bioLinks: [String]?
public let currentAvatarImageUrl: String?
public let currentAvatarThumbnailImageUrl: String?
public typealias Tag = String

public struct UserDetail {
public var bio: String?
public var bioLinks: SafeDecodingArray<URL>
public let avatarImageUrl: URL?
public let avatarThumbnailUrl: URL?
public let displayName: String
public let id: String
public let isFriend: Bool
public let lastLogin: Date
public let lastPlatform: String
public let profilePicOverride: String?
public let profilePicOverride: URL?
public let state: User.State
public let status: UserStatus
public let statusDescription: String
public let tags: [String]
public let userIcon: String?
public var statusDescription: String
public var tags: [Tag]
public let userIcon: URL?
public let location: String
public let friendKey: String
public let dateJoined: String
public var note: String
public let lastActivity: Date
}

extension UserDetail: Codable {
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
bio = try container.decodeIfPresent(String.self, forKey: .bio)
bioLinks = try container.decodeSafeNullableArray(URL.self, forKey: .bioLinks)
avatarImageUrl = try? container.decodeIfPresent(URL.self, forKey: .avatarImageUrl)
avatarThumbnailUrl = try? container.decodeIfPresent(URL.self, forKey: .avatarThumbnailUrl)
displayName = try container.decode(String.self, forKey: .displayName)
id = try container.decode(String.self, forKey: .id)
isFriend = try container.decode(Bool.self, forKey: .isFriend)
lastLogin = try container.decode(Date.self, forKey: .lastLogin)
lastPlatform = try container.decode(String.self, forKey: .lastPlatform)
profilePicOverride = try? container.decodeIfPresent(URL.self, forKey: .profilePicOverride)
state = try container.decode(User.State.self, forKey: .state)
status = try container.decode(UserStatus.self, forKey: .status)
statusDescription = try container.decode(String.self, forKey: .statusDescription)
tags = try container.decode([Tag].self, forKey: .tags)
userIcon = try? container.decodeIfPresent(URL.self, forKey: .userIcon)
location = try container.decode(String.self, forKey: .location)
friendKey = try container.decode(String.self, forKey: .friendKey)
dateJoined = try container.decode(String.self, forKey: .dateJoined)
note = try container.decode(String.self, forKey: .note)
lastActivity = try container.decode(Date.self, forKey: .lastActivity)
}
}

extension UserDetail {
private enum CodingKeys: String, CodingKey {
case bio
case bioLinks
case avatarImageUrl = "currentAvatarImageUrl"
case avatarThumbnailUrl = "currentAvatarThumbnailImageUrl"
case displayName
case id
case isFriend
case lastLogin
case lastPlatform
case profilePicOverride
case state
case status
case statusDescription
case tags
case userIcon
case location
case friendKey
case dateJoined
case note
case lastActivity
}
}

public struct EditableUserInfo: Codable, Hashable {
public var bio: String
public var bioLinks: [URL]
public var status: UserStatus
public var statusDescription: String
public var tags: [Tag]

public init(detail: any ProfileDetailRepresentable) {
bio = detail.bio ?? ""
bioLinks = detail.bioLinks.elements
status = detail.status
statusDescription = detail.statusDescription
tags = detail.tags
}
}
84 changes: 76 additions & 8 deletions Sources/VRCKit/Models/UserModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@

import Foundation

public struct User: Codable, ProfileDetailRepresentable {
public struct User: ProfileDetailRepresentable {
public let activeFriends: [String]
public let allowAvatarCopying: Bool
public let bio: String?
public let bioLinks: [String]?
public var bioLinks: SafeDecodingArray<URL>
public let currentAvatar: String
public let currentAvatarAssetUrl: String
public let currentAvatarImageUrl: String?
public let currentAvatarThumbnailImageUrl: String?
public let avatarImageUrl: URL?
public let avatarThumbnailUrl: URL?
public let dateJoined: String
public let displayName: String
public let friendKey: String
Expand All @@ -29,13 +28,13 @@ public struct User: Codable, ProfileDetailRepresentable {
public let offlineFriends: [String]
public let onlineFriends: [String]
public let pastDisplayNames: [DisplayName]
public let profilePicOverride: String?
public let profilePicOverride: URL?
public let state: State
public let status: UserStatus
public let statusDescription: String
public let tags: [String]
public let tags: [Tag]
public let twoFactorAuthEnabled: Bool
public let userIcon: String?
public let userIcon: URL?
public let userLanguage: String?
public let userLanguageCode: String?

Expand All @@ -54,6 +53,75 @@ public struct User: Codable, ProfileDetailRepresentable {
}
}

extension User: Codable {
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
activeFriends = try container.decode([String].self, forKey: .activeFriends)
allowAvatarCopying = try container.decode(Bool.self, forKey: .allowAvatarCopying)
bio = try container.decodeIfPresent(String.self, forKey: .bio)
bioLinks = try container.decodeSafeNullableArray(URL.self, forKey: .bioLinks)
currentAvatar = try container.decode(String.self, forKey: .currentAvatar)
avatarImageUrl = try? container.decodeIfPresent(URL.self, forKey: .avatarImageUrl)
avatarThumbnailUrl = try? container.decodeIfPresent(URL.self, forKey: .avatarThumbnailUrl)
dateJoined = try container.decode(String.self, forKey: .dateJoined)
displayName = try container.decode(String.self, forKey: .displayName)
friendKey = try container.decode(String.self, forKey: .friendKey)
friends = try container.decode([String].self, forKey: .friends)
homeLocation = try container.decode(String.self, forKey: .homeLocation)
id = try container.decode(String.self, forKey: .id)
isFriend = try container.decode(Bool.self, forKey: .isFriend)
lastActivity = try container.decode(Date.self, forKey: .lastActivity)
lastLogin = try container.decode(Date.self, forKey: .lastLogin)
lastPlatform = try container.decode(String.self, forKey: .lastPlatform)
offlineFriends = try container.decode([String].self, forKey: .offlineFriends)
onlineFriends = try container.decode([String].self, forKey: .onlineFriends)
pastDisplayNames = try container.decode([User.DisplayName].self, forKey: .pastDisplayNames)
profilePicOverride = try? container.decodeIfPresent(URL.self, forKey: .profilePicOverride)
state = try container.decode(User.State.self, forKey: .state)
status = try container.decode(UserStatus.self, forKey: .status)
statusDescription = try container.decode(String.self, forKey: .statusDescription)
tags = try container.decode([Tag].self, forKey: .tags)
twoFactorAuthEnabled = try container.decode(Bool.self, forKey: .twoFactorAuthEnabled)
userIcon = try? container.decodeIfPresent(URL.self, forKey: .userIcon)
userLanguage = try container.decodeIfPresent(String.self, forKey: .userLanguage)
userLanguageCode = try container.decodeIfPresent(String.self, forKey: .userLanguageCode)
}
}

extension User {
private enum CodingKeys: String, CodingKey {
case activeFriends
case allowAvatarCopying
case bio
case bioLinks
case currentAvatar
case avatarImageUrl = "currentAvatarImageUrl"
case avatarThumbnailUrl = "currentAvatarThumbnailImageUrl"
case dateJoined
case displayName
case friendKey
case friends
case homeLocation
case id
case isFriend
case lastActivity
case lastLogin
case lastPlatform
case offlineFriends
case onlineFriends
case pastDisplayNames
case profilePicOverride
case state
case status
case statusDescription
case tags
case twoFactorAuthEnabled
case userIcon
case userLanguage
case userLanguageCode
}
}

public struct UpdatedUser: Codable {
public let bio: String?
public let statusDescription: String?
Expand Down
25 changes: 12 additions & 13 deletions Sources/VRCKit/PreviewServices/PreviewDataProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,10 @@ final class PreviewDataProvider {
activeFriends: onlineFriends.map(\.id),
allowAvatarCopying: false,
bio: "This is the demo user.",
bioLinks: ["https://example.com"],
bioLinks: SafeDecodingArray(),
currentAvatar: "",
currentAvatarAssetUrl: "",
currentAvatarImageUrl: "",
currentAvatarThumbnailImageUrl: "",
avatarImageUrl: nil,
avatarThumbnailUrl: nil,
dateJoined: "2024/07/01",
displayName: "usr_\(previewUserId.uuidString.prefix(8))",
friendKey: "",
Expand All @@ -87,7 +86,7 @@ final class PreviewDataProvider {
statusDescription: "status",
tags: [],
twoFactorAuthEnabled: true,
userIcon: "https://ul.h3z.jp/9gGIcerr.png",
userIcon: URL(string: "https://ul.h3z.jp/9gGIcerr.png"),
userLanguage: nil,
userLanguageCode: nil
)
Expand Down Expand Up @@ -120,9 +119,9 @@ final class PreviewDataProvider {
) -> Friend {
Friend(
bio: nil,
bioLinks: nil,
currentAvatarImageUrl: nil,
currentAvatarThumbnailImageUrl: nil,
bioLinks: SafeDecodingArray(),
avatarImageUrl: nil,
avatarThumbnailUrl: nil,
displayName: "User_\(id.uuidString.prefix(8))",
id: "usr_\(id.uuidString)",
isFriend: true,
Expand All @@ -132,7 +131,7 @@ final class PreviewDataProvider {
status: status,
statusDescription: "",
tags: [],
userIcon: "https://ul.h3z.jp/9gGIcerr.png",
userIcon: URL(string: "https://ul.h3z.jp/9gGIcerr.png"),
location: location,
friendKey: ""
)
Expand All @@ -147,9 +146,9 @@ final class PreviewDataProvider {
) -> UserDetail {
UserDetail(
bio: "Demo",
bioLinks: [],
currentAvatarImageUrl: nil,
currentAvatarThumbnailImageUrl: nil,
bioLinks: SafeDecodingArray(),
avatarImageUrl: nil,
avatarThumbnailUrl: nil,
displayName: "User_\(id.uuidString.prefix(8))",
id: "usr_\(id.uuidString)",
isFriend: isFriend,
Expand All @@ -160,7 +159,7 @@ final class PreviewDataProvider {
status: status,
statusDescription: "Demo",
tags: [],
userIcon: "https://ul.h3z.jp/9gGIcerr.png",
userIcon: URL(string: "https://ul.h3z.jp/9gGIcerr.png"),
location: location,
friendKey: "",
dateJoined: "",
Expand Down
Loading

0 comments on commit fd8281e

Please sign in to comment.