From 00848bd4dea896f0b4fca5fe11eb14c5754dc82d Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 8 Oct 2024 15:29:22 +0300 Subject: [PATCH 01/15] Release 3.0.0-rc.1 (#472) --- Sources/Gravatar/Resources/SDKInfo.plist | 2 +- version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Gravatar/Resources/SDKInfo.plist b/Sources/Gravatar/Resources/SDKInfo.plist index e1013fac..0bccac01 100644 --- a/Sources/Gravatar/Resources/SDKInfo.plist +++ b/Sources/Gravatar/Resources/SDKInfo.plist @@ -3,6 +3,6 @@ CFBundleShortVersionString - 2.1.1 + 3.0.0-rc.1 diff --git a/version.rb b/version.rb index 49dc2173..9f1943bb 100644 --- a/version.rb +++ b/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Gravatar - VERSION = '2.1.1' + VERSION = '3.0.0-rc.1' SWIFT_VERSIONS = [ '5.10' ].freeze From e3f12f04f1c1dcb6b45f994c7e1dba6e9a1dcf46 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 8 Oct 2024 17:09:10 +0300 Subject: [PATCH 02/15] Trigger Build From c949ca75e4daf968ddb9a7f5b8050935f6e714ce Mon Sep 17 00:00:00 2001 From: etoledom Date: Wed, 9 Oct 2024 11:17:57 +0200 Subject: [PATCH 03/15] Remove open-api deprecations (#475) --- .../Generated/AssociatedResponse.swift | 16 +-- .../Gravatar/OpenApi/Generated/Avatar.swift | 28 +---- .../Generated/CryptoWalletAddress.swift | 22 +--- .../OpenApi/Generated/GalleryImage.swift | 25 +--- .../Gravatar/OpenApi/Generated/Interest.swift | 22 +--- .../Gravatar/OpenApi/Generated/Language.swift | 26 +---- Sources/Gravatar/OpenApi/Generated/Link.swift | 22 +--- .../OpenApi/Generated/ModelError.swift | 18 +-- .../Gravatar/OpenApi/Generated/Profile.swift | 110 +----------------- .../Generated/ProfileContactInfo.swift | 30 +---- .../OpenApi/Generated/ProfilePayments.swift | 22 +--- .../Generated/SetEmailAvatarRequest.swift | 16 +-- .../OpenApi/Generated/VerifiedAccount.swift | 36 +----- openapi/modelObject.mustache | 51 +------- 14 files changed, 37 insertions(+), 407 deletions(-) diff --git a/Sources/Gravatar/OpenApi/Generated/AssociatedResponse.swift b/Sources/Gravatar/OpenApi/Generated/AssociatedResponse.swift index 720277a3..f5587bf0 100644 --- a/Sources/Gravatar/OpenApi/Generated/AssociatedResponse.swift +++ b/Sources/Gravatar/OpenApi/Generated/AssociatedResponse.swift @@ -4,32 +4,18 @@ struct AssociatedResponse: Codable, Hashable, Sendable { /// Whether the entity is associated with the account. private(set) var associated: Bool - @available(*, deprecated, message: "init will become internal on the next release") init(associated: Bool) { self.associated = associated } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") enum CodingKeys: String, CodingKey, CaseIterable { case associated } - enum InternalCodingKeys: String, CodingKey, CaseIterable { - case associated - } - // Encodable protocol methods func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(associated, forKey: .associated) } - - // Decodable protocol methods - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - associated = try container.decode(Bool.self, forKey: .associated) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/Avatar.swift b/Sources/Gravatar/OpenApi/Generated/Avatar.swift index 9441b531..898b8758 100644 --- a/Sources/Gravatar/OpenApi/Generated/Avatar.swift +++ b/Sources/Gravatar/OpenApi/Generated/Avatar.swift @@ -23,7 +23,6 @@ package struct Avatar: Codable, Hashable, Sendable { /// Whether the image is currently selected as the provided selected email's avatar. package private(set) var selected: Bool? - @available(*, deprecated, message: "init will become internal on the next release") package init(imageId: String, imageUrl: String, rating: Rating, updatedDate: Date, altText: String, selected: Bool? = nil) { self.imageId = imageId self.imageUrl = imageUrl @@ -33,17 +32,7 @@ package struct Avatar: Codable, Hashable, Sendable { self.selected = selected } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - package enum CodingKeys: String, CodingKey, CaseIterable { - case imageId = "image_id" - case imageUrl = "image_url" - case rating - case updatedDate = "updated_date" - case altText = "alt_text" - case selected - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case imageId = "image_id" case imageUrl = "image_url" case rating @@ -55,7 +44,7 @@ package struct Avatar: Codable, Hashable, Sendable { // Encodable protocol methods package func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(imageId, forKey: .imageId) try container.encode(imageUrl, forKey: .imageUrl) try container.encode(rating, forKey: .rating) @@ -63,17 +52,4 @@ package struct Avatar: Codable, Hashable, Sendable { try container.encode(altText, forKey: .altText) try container.encodeIfPresent(selected, forKey: .selected) } - - // Decodable protocol methods - - package init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - imageId = try container.decode(String.self, forKey: .imageId) - imageUrl = try container.decode(String.self, forKey: .imageUrl) - rating = try container.decode(Rating.self, forKey: .rating) - updatedDate = try container.decode(Date.self, forKey: .updatedDate) - altText = try container.decode(String.self, forKey: .altText) - selected = try container.decodeIfPresent(Bool.self, forKey: .selected) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/CryptoWalletAddress.swift b/Sources/Gravatar/OpenApi/Generated/CryptoWalletAddress.swift index ddafe110..280035a1 100644 --- a/Sources/Gravatar/OpenApi/Generated/CryptoWalletAddress.swift +++ b/Sources/Gravatar/OpenApi/Generated/CryptoWalletAddress.swift @@ -8,19 +8,12 @@ public struct CryptoWalletAddress: Codable, Hashable, Sendable { /// The wallet address for the crypto currency. public private(set) var address: String - @available(*, deprecated, message: "init will become internal on the next release") - public init(label: String, address: String) { + init(label: String, address: String) { self.label = label self.address = address } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case label - case address - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case label case address } @@ -28,17 +21,8 @@ public struct CryptoWalletAddress: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(label, forKey: .label) try container.encode(address, forKey: .address) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - label = try container.decode(String.self, forKey: .label) - address = try container.decode(String.self, forKey: .address) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/GalleryImage.swift b/Sources/Gravatar/OpenApi/Generated/GalleryImage.swift index 104ff05c..fc305a60 100644 --- a/Sources/Gravatar/OpenApi/Generated/GalleryImage.swift +++ b/Sources/Gravatar/OpenApi/Generated/GalleryImage.swift @@ -8,24 +8,12 @@ public struct GalleryImage: Codable, Hashable, Sendable { /// The image alt text. public private(set) var altText: String? - @available(*, deprecated, message: "init will become internal on the next release") - public init(url: String) { - self.url = url - } - - // NOTE: This init is maintained manually. - // Avoid deleting this init until the deprecation of is applied. init(url: String, altText: String? = nil) { self.url = url self.altText = altText } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case url - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case url case altText = "alt_text" } @@ -33,17 +21,8 @@ public struct GalleryImage: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(url, forKey: .url) try container.encodeIfPresent(altText, forKey: .altText) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - url = try container.decode(String.self, forKey: .url) - altText = try container.decodeIfPresent(String.self, forKey: .altText) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/Interest.swift b/Sources/Gravatar/OpenApi/Generated/Interest.swift index 2ce8cef5..d6257d5a 100644 --- a/Sources/Gravatar/OpenApi/Generated/Interest.swift +++ b/Sources/Gravatar/OpenApi/Generated/Interest.swift @@ -8,19 +8,12 @@ public struct Interest: Codable, Hashable, Sendable { /// The name of the interest. public private(set) var name: String - @available(*, deprecated, message: "init will become internal on the next release") - public init(id: Int, name: String) { + init(id: Int, name: String) { self.id = id self.name = name } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case id - case name - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case id case name } @@ -28,17 +21,8 @@ public struct Interest: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - id = try container.decode(Int.self, forKey: .id) - name = try container.decode(String.self, forKey: .name) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/Language.swift b/Sources/Gravatar/OpenApi/Generated/Language.swift index 95f70c9f..8fc66801 100644 --- a/Sources/Gravatar/OpenApi/Generated/Language.swift +++ b/Sources/Gravatar/OpenApi/Generated/Language.swift @@ -12,23 +12,14 @@ public struct Language: Codable, Hashable, Sendable { /// The order of the language in the user's profile. public private(set) var order: Int - @available(*, deprecated, message: "init will become internal on the next release") - public init(code: String, name: String, isPrimary: Bool, order: Int) { + init(code: String, name: String, isPrimary: Bool, order: Int) { self.code = code self.name = name self.isPrimary = isPrimary self.order = order } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case code - case name - case isPrimary = "is_primary" - case order - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case code case name case isPrimary = "is_primary" @@ -38,21 +29,10 @@ public struct Language: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(code, forKey: .code) try container.encode(name, forKey: .name) try container.encode(isPrimary, forKey: .isPrimary) try container.encode(order, forKey: .order) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - code = try container.decode(String.self, forKey: .code) - name = try container.decode(String.self, forKey: .name) - isPrimary = try container.decode(Bool.self, forKey: .isPrimary) - order = try container.decode(Int.self, forKey: .order) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/Link.swift b/Sources/Gravatar/OpenApi/Generated/Link.swift index 27fd4b81..d2f153c4 100644 --- a/Sources/Gravatar/OpenApi/Generated/Link.swift +++ b/Sources/Gravatar/OpenApi/Generated/Link.swift @@ -8,19 +8,12 @@ public struct Link: Codable, Hashable, Sendable { /// The URL to the link. public private(set) var url: String - @available(*, deprecated, message: "init will become internal on the next release") - public init(label: String, url: String) { + init(label: String, url: String) { self.label = label self.url = url } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case label - case url - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case label case url } @@ -28,17 +21,8 @@ public struct Link: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(label, forKey: .label) try container.encode(url, forKey: .url) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - label = try container.decode(String.self, forKey: .label) - url = try container.decode(String.self, forKey: .url) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/ModelError.swift b/Sources/Gravatar/OpenApi/Generated/ModelError.swift index 7da1c59a..b180a823 100644 --- a/Sources/Gravatar/OpenApi/Generated/ModelError.swift +++ b/Sources/Gravatar/OpenApi/Generated/ModelError.swift @@ -8,37 +8,21 @@ struct ModelError: Codable, Hashable, Sendable { /// The error code for the error message private(set) var code: String? - @available(*, deprecated, message: "init will become internal on the next release") init(error: String, code: String? = nil) { self.error = error self.code = code } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") enum CodingKeys: String, CodingKey, CaseIterable { case error case code } - enum InternalCodingKeys: String, CodingKey, CaseIterable { - case error - case code - } - // Encodable protocol methods func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(error, forKey: .error) try container.encodeIfPresent(code, forKey: .code) } - - // Decodable protocol methods - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - error = try container.decode(String.self, forKey: .error) - code = try container.decodeIfPresent(String.self, forKey: .code) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/Profile.swift b/Sources/Gravatar/OpenApi/Generated/Profile.swift index 84880eb8..0c2b886b 100644 --- a/Sources/Gravatar/OpenApi/Generated/Profile.swift +++ b/Sources/Gravatar/OpenApi/Generated/Profile.swift @@ -53,51 +53,6 @@ public struct Profile: Codable, Hashable, Sendable { /// The date the user registered their account. This is only provided in authenticated API requests. public private(set) var registrationDate: Date? - @available(*, deprecated, message: "init will become internal on the next release") - public init( - hash: String, - displayName: String, - profileUrl: String, - avatarUrl: String, - avatarAltText: String, - location: String, - description: String, - jobTitle: String, - company: String, - verifiedAccounts: [VerifiedAccount], - pronunciation: String, - pronouns: String, - links: [Link]? = nil, - payments: ProfilePayments? = nil, - contactInfo: ProfileContactInfo? = nil, - gallery: [GalleryImage]? = nil, - numberVerifiedAccounts: Int? = nil, - lastProfileEdit: Date? = nil, - registrationDate: Date? = nil - ) { - self.hash = hash - self.displayName = displayName - self.profileUrl = profileUrl - self.avatarUrl = avatarUrl - self.avatarAltText = avatarAltText - self.location = location - self.description = description - self.jobTitle = jobTitle - self.company = company - self.verifiedAccounts = verifiedAccounts - self.pronunciation = pronunciation - self.pronouns = pronouns - self.links = links - self.payments = payments - self.contactInfo = contactInfo - self.gallery = gallery - self.numberVerifiedAccounts = numberVerifiedAccounts - self.lastProfileEdit = lastProfileEdit - self.registrationDate = registrationDate - } - - // NOTE: This init is maintained manually. - // Avoid deleting this init until the deprecation is applied. init( hash: String, displayName: String, @@ -152,36 +107,7 @@ public struct Profile: Codable, Hashable, Sendable { self.registrationDate = registrationDate } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case hash - case displayName = "display_name" - case profileUrl = "profile_url" - case avatarUrl = "avatar_url" - case avatarAltText = "avatar_alt_text" - case location - case description - case jobTitle = "job_title" - case company - case verifiedAccounts = "verified_accounts" - case pronunciation - case pronouns - case timezone - case languages - case firstName = "first_name" - case lastName = "last_name" - case isOrganization = "is_organization" - case links - case interests - case payments - case contactInfo = "contact_info" - case gallery - case numberVerifiedAccounts = "number_verified_accounts" - case lastProfileEdit = "last_profile_edit" - case registrationDate = "registration_date" - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case hash case displayName = "display_name" case profileUrl = "profile_url" @@ -212,7 +138,7 @@ public struct Profile: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(hash, forKey: .hash) try container.encode(displayName, forKey: .displayName) try container.encode(profileUrl, forKey: .profileUrl) @@ -239,36 +165,4 @@ public struct Profile: Codable, Hashable, Sendable { try container.encodeIfPresent(lastProfileEdit, forKey: .lastProfileEdit) try container.encodeIfPresent(registrationDate, forKey: .registrationDate) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - hash = try container.decode(String.self, forKey: .hash) - displayName = try container.decode(String.self, forKey: .displayName) - profileUrl = try container.decode(String.self, forKey: .profileUrl) - avatarUrl = try container.decode(String.self, forKey: .avatarUrl) - avatarAltText = try container.decode(String.self, forKey: .avatarAltText) - location = try container.decode(String.self, forKey: .location) - description = try container.decode(String.self, forKey: .description) - jobTitle = try container.decode(String.self, forKey: .jobTitle) - company = try container.decode(String.self, forKey: .company) - verifiedAccounts = try container.decode([VerifiedAccount].self, forKey: .verifiedAccounts) - pronunciation = try container.decode(String.self, forKey: .pronunciation) - pronouns = try container.decode(String.self, forKey: .pronouns) - timezone = try container.decodeIfPresent(String.self, forKey: .timezone) - languages = try container.decodeIfPresent([Language].self, forKey: .languages) - firstName = try container.decodeIfPresent(String.self, forKey: .firstName) - lastName = try container.decodeIfPresent(String.self, forKey: .lastName) - isOrganization = try container.decodeIfPresent(Bool.self, forKey: .isOrganization) - links = try container.decodeIfPresent([Link].self, forKey: .links) - interests = try container.decodeIfPresent([Interest].self, forKey: .interests) - payments = try container.decodeIfPresent(ProfilePayments.self, forKey: .payments) - contactInfo = try container.decodeIfPresent(ProfileContactInfo.self, forKey: .contactInfo) - gallery = try container.decodeIfPresent([GalleryImage].self, forKey: .gallery) - numberVerifiedAccounts = try container.decodeIfPresent(Int.self, forKey: .numberVerifiedAccounts) - lastProfileEdit = try container.decodeIfPresent(Date.self, forKey: .lastProfileEdit) - registrationDate = try container.decodeIfPresent(Date.self, forKey: .registrationDate) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/ProfileContactInfo.swift b/Sources/Gravatar/OpenApi/Generated/ProfileContactInfo.swift index 2895a6e5..bfddf11f 100644 --- a/Sources/Gravatar/OpenApi/Generated/ProfileContactInfo.swift +++ b/Sources/Gravatar/OpenApi/Generated/ProfileContactInfo.swift @@ -16,8 +16,7 @@ public struct ProfileContactInfo: Codable, Hashable, Sendable { /// The URL to the user's calendar. public private(set) var calendar: String? - @available(*, deprecated, message: "init will become internal on the next release") - public init( + init( homePhone: String? = nil, workPhone: String? = nil, cellPhone: String? = nil, @@ -33,17 +32,7 @@ public struct ProfileContactInfo: Codable, Hashable, Sendable { self.calendar = calendar } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case homePhone = "home_phone" - case workPhone = "work_phone" - case cellPhone = "cell_phone" - case email - case contactForm = "contact_form" - case calendar - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case homePhone = "home_phone" case workPhone = "work_phone" case cellPhone = "cell_phone" @@ -55,7 +44,7 @@ public struct ProfileContactInfo: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(homePhone, forKey: .homePhone) try container.encodeIfPresent(workPhone, forKey: .workPhone) try container.encodeIfPresent(cellPhone, forKey: .cellPhone) @@ -63,17 +52,4 @@ public struct ProfileContactInfo: Codable, Hashable, Sendable { try container.encodeIfPresent(contactForm, forKey: .contactForm) try container.encodeIfPresent(calendar, forKey: .calendar) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - homePhone = try container.decodeIfPresent(String.self, forKey: .homePhone) - workPhone = try container.decodeIfPresent(String.self, forKey: .workPhone) - cellPhone = try container.decodeIfPresent(String.self, forKey: .cellPhone) - email = try container.decodeIfPresent(String.self, forKey: .email) - contactForm = try container.decodeIfPresent(String.self, forKey: .contactForm) - calendar = try container.decodeIfPresent(String.self, forKey: .calendar) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/ProfilePayments.swift b/Sources/Gravatar/OpenApi/Generated/ProfilePayments.swift index 841afe2d..d3306ca8 100644 --- a/Sources/Gravatar/OpenApi/Generated/ProfilePayments.swift +++ b/Sources/Gravatar/OpenApi/Generated/ProfilePayments.swift @@ -8,19 +8,12 @@ public struct ProfilePayments: Codable, Hashable, Sendable { /// A list of crypto currencies the user accepts. public private(set) var cryptoWallets: [CryptoWalletAddress] - @available(*, deprecated, message: "init will become internal on the next release") - public init(links: [Link], cryptoWallets: [CryptoWalletAddress]) { + init(links: [Link], cryptoWallets: [CryptoWalletAddress]) { self.links = links self.cryptoWallets = cryptoWallets } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case links - case cryptoWallets = "crypto_wallets" - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case links case cryptoWallets = "crypto_wallets" } @@ -28,17 +21,8 @@ public struct ProfilePayments: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(links, forKey: .links) try container.encode(cryptoWallets, forKey: .cryptoWallets) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - links = try container.decode([Link].self, forKey: .links) - cryptoWallets = try container.decode([CryptoWalletAddress].self, forKey: .cryptoWallets) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/SetEmailAvatarRequest.swift b/Sources/Gravatar/OpenApi/Generated/SetEmailAvatarRequest.swift index 8b9b0662..a2cb31a5 100644 --- a/Sources/Gravatar/OpenApi/Generated/SetEmailAvatarRequest.swift +++ b/Sources/Gravatar/OpenApi/Generated/SetEmailAvatarRequest.swift @@ -4,22 +4,18 @@ struct SetEmailAvatarRequest: Codable, Hashable, Sendable { /// The email SHA256 hash to set the avatar for. private(set) var emailHash: String - enum InternalCodingKeys: String, CodingKey, CaseIterable { + init(emailHash: String) { + self.emailHash = emailHash + } + + enum CodingKeys: String, CodingKey, CaseIterable { case emailHash = "email_hash" } // Encodable protocol methods func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(emailHash, forKey: .emailHash) } - - // Decodable protocol methods - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - emailHash = try container.decode(String.self, forKey: .emailHash) - } } diff --git a/Sources/Gravatar/OpenApi/Generated/VerifiedAccount.swift b/Sources/Gravatar/OpenApi/Generated/VerifiedAccount.swift index b0a58047..ce35f995 100644 --- a/Sources/Gravatar/OpenApi/Generated/VerifiedAccount.swift +++ b/Sources/Gravatar/OpenApi/Generated/VerifiedAccount.swift @@ -14,19 +14,6 @@ public struct VerifiedAccount: Codable, Hashable, Sendable { /// Whether the verified account is hidden from the user's profile. public private(set) var isHidden: Bool - // NOTE: This init is maintained manually. - // Avoid deleting this init until the deprecation of is applied. - @available(*, deprecated, message: "init will become internal on the next release") - public init(serviceLabel: String, serviceIcon: String, url: String) { - self.init( - serviceType: "", - serviceLabel: serviceLabel, - serviceIcon: serviceIcon, - url: url, - isHidden: false - ) - } - init(serviceType: String, serviceLabel: String, serviceIcon: String, url: String, isHidden: Bool) { self.serviceType = serviceType self.serviceLabel = serviceLabel @@ -35,14 +22,7 @@ public struct VerifiedAccount: Codable, Hashable, Sendable { self.isHidden = isHidden } - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - public enum CodingKeys: String, CodingKey, CaseIterable { - case serviceLabel = "service_label" - case serviceIcon = "service_icon" - case url - } - - enum InternalCodingKeys: String, CodingKey, CaseIterable { + enum CodingKeys: String, CodingKey, CaseIterable { case serviceType = "service_type" case serviceLabel = "service_label" case serviceIcon = "service_icon" @@ -53,23 +33,11 @@ public struct VerifiedAccount: Codable, Hashable, Sendable { // Encodable protocol methods public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(serviceType, forKey: .serviceType) try container.encode(serviceLabel, forKey: .serviceLabel) try container.encode(serviceIcon, forKey: .serviceIcon) try container.encode(url, forKey: .url) try container.encode(isHidden, forKey: .isHidden) } - - // Decodable protocol methods - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - serviceType = try container.decode(String.self, forKey: .serviceType) - serviceLabel = try container.decode(String.self, forKey: .serviceLabel) - serviceIcon = try container.decode(String.self, forKey: .serviceIcon) - url = try container.decode(String.self, forKey: .url) - isHidden = try container.decode(Bool.self, forKey: .isHidden) - } } diff --git a/openapi/modelObject.mustache b/openapi/modelObject.mustache index 0a339ffd..03066346 100644 --- a/openapi/modelObject.mustache +++ b/openapi/modelObject.mustache @@ -51,18 +51,14 @@ {{/allVars}} {{#hasVars}} -{{! DEPRECARED public init }} - @available(*, deprecated, message: "init will become internal on the next release") - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init({{#allVars}}{{{name}}}: {{#vendorExtensions.x-null-encodable}}NullEncodable<{{{datatypeWithEnum}}}>{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{datatypeWithEnum}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}{{#defaultValue}} = {{#vendorExtensions.x-null-encodable}}{{{vendorExtensions.x-null-encodable-default-value}}}{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{.}}}{{/vendorExtensions.x-null-encodable}}{{/defaultValue}}{{^defaultValue}}{{^required}} = {{#vendorExtensions.x-null-encodable}}.encodeNull{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}nil{{/vendorExtensions.x-null-encodable}}{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allVars}}) { + init({{#allVars}}{{{name}}}: {{#vendorExtensions.x-null-encodable}}NullEncodable<{{{datatypeWithEnum}}}>{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{datatypeWithEnum}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}{{#defaultValue}} = {{#vendorExtensions.x-null-encodable}}{{{vendorExtensions.x-null-encodable-default-value}}}{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{.}}}{{/vendorExtensions.x-null-encodable}}{{/defaultValue}}{{^defaultValue}}{{^required}} = {{#vendorExtensions.x-null-encodable}}.encodeNull{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}nil{{/vendorExtensions.x-null-encodable}}{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allVars}}) { {{#allVars}} self.{{{name}}} = {{{name}}} {{/allVars}} } {{/hasVars}} -{{! DEPRECARED public coding keys }} - @available(*, deprecated, message: "CodingKeys will become internal on the next release.") - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum CodingKeys: {{#hasVars}}String, {{/hasVars}}CodingKey, CaseIterable { + internal enum CodingKeys: {{#hasVars}}String, {{/hasVars}}CodingKey, CaseIterable { {{#allVars}} case {{{name}}}{{#vendorExtensions.x-codegen-escaped-property-name}} = "{{{baseName}}}"{{/vendorExtensions.x-codegen-escaped-property-name}} {{/allVars}} @@ -83,40 +79,10 @@ } }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} - - -{{!----- BEGINNING OF INTERNAL CODING KEYS ------ }} -{{! When the public CodingKeys get removed, we can rename this one back to "CodingKeys", making it internal }} - - internal enum InternalCodingKeys: {{#hasVars}}String, {{/hasVars}}CodingKey, CaseIterable { - {{#allVars}} - case {{{name}}}{{#vendorExtensions.x-codegen-escaped-property-name}} = "{{{baseName}}}"{{/vendorExtensions.x-codegen-escaped-property-name}} - {{/allVars}} - }{{#generateModelAdditionalProperties}}{{#additionalPropertiesType}} - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var additionalProperties: [String: {{{additionalPropertiesType}}}] = [:] - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} subscript(key: String) -> {{{additionalPropertiesType}}}? { - get { - if let value = additionalProperties[key] { - return value - } - return nil - } - - set { - additionalProperties[key] = newValue - } - }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} - -{{!----- END OF INTERNAL CODING KEYS ------ }} - - - // Encodable protocol methods {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: InternalCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) {{#allVars}} {{#vendorExtensions.x-null-encodable}} switch {{{name}}} { @@ -137,19 +103,8 @@ } {{#generateModelAdditionalProperties}} - - // Decodable protocol methods - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: InternalCodingKeys.self) - - {{#allVars}} - {{{name}}} = try container.decode{{#required}}{{#isNullable}}IfPresent{{/isNullable}}{{/required}}{{^required}}IfPresent{{/required}}({{{datatypeWithEnum}}}.self, forKey: .{{{name}}}) - {{/allVars}} - } {{/generateModelAdditionalProperties}} - {{^objcCompatible}}{{#useClasses}}{{#vendorExtensions.x-swift-hashable}} {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func == (lhs: {{classname}}, rhs: {{classname}}) -> Bool { From 00358fe9fea707322ec8531964a67f667a290d88 Mon Sep 17 00:00:00 2001 From: etoledom Date: Wed, 9 Oct 2024 11:18:15 +0200 Subject: [PATCH 04/15] Making some access to OAuthSession static (#473) --- .../DemoQuickEditorViewController.swift | 6 ++---- .../SwiftUI/OAuthSession/OAuthSession.swift | 15 ++++++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Demo/Demo/Gravatar-UIKit-Demo/DemoQuickEditorViewController.swift b/Demo/Demo/Gravatar-UIKit-Demo/DemoQuickEditorViewController.swift index aa5f7557..f836200f 100644 --- a/Demo/Demo/Gravatar-UIKit-Demo/DemoQuickEditorViewController.swift +++ b/Demo/Demo/Gravatar-UIKit-Demo/DemoQuickEditorViewController.swift @@ -125,18 +125,16 @@ final class DemoQuickEditorViewController: UIViewController { func updateLogoutButton(_ button: UIButton? = nil) { guard let savedEmail else { return } - let session = OAuthSession() let button = button ?? logoutButton UIView.animate { - button.isHidden = !session.hasSession(with: Email(savedEmail)) + button.isHidden = !OAuthSession.hasSession(with: Email(savedEmail)) button.alpha = button.isHidden ? 0 : 1 } } func logout() { guard let savedEmail else { return } - let session = OAuthSession() - session.deleteSession(with: Email(savedEmail)) + OAuthSession.deleteSession(with: Email(savedEmail)) updateLogoutButton() } diff --git a/Sources/GravatarUI/SwiftUI/OAuthSession/OAuthSession.swift b/Sources/GravatarUI/SwiftUI/OAuthSession/OAuthSession.swift index 593df22b..d409595e 100644 --- a/Sources/GravatarUI/SwiftUI/OAuthSession/OAuthSession.swift +++ b/Sources/GravatarUI/SwiftUI/OAuthSession/OAuthSession.swift @@ -1,6 +1,8 @@ import AuthenticationServices public struct OAuthSession: Sendable { + private static let shared = OAuthSession() + private let storage: SecureStorage private let authenticationSession: AuthenticationSession private let snakeCaseDecoder: JSONDecoder = { @@ -14,15 +16,14 @@ public struct OAuthSession: Sendable { self.storage = storage } - public init() { - self.authenticationSession = OldAuthenticationSession() - self.storage = Keychain() - } - public func hasSession(with email: Email) -> Bool { (try? storage.secret(with: email.rawValue) ?? nil) != nil } + public static func hasSession(with email: Email) -> Bool { + shared.hasSession(with: email) + } + func hasValidSession(with email: Email) -> Bool { guard let token = try? storage.secret(with: email.rawValue) else { return false @@ -45,6 +46,10 @@ public struct OAuthSession: Sendable { try? storage.deleteSecret(with: email.rawValue) } + public static func deleteSession(with email: Email) { + shared.deleteSession(with: email) + } + func sessionToken(with email: Email) -> KeychainToken? { try? storage.secret(with: email.rawValue) } From 9c2938024d4fd5e8c3edd79a9f6bb10761a057e0 Mon Sep 17 00:00:00 2001 From: etoledom Date: Wed, 9 Oct 2024 15:09:29 +0200 Subject: [PATCH 05/15] Remove global deprecations (#481) --- .../Gravatar/Network/Services/AvatarService.swift | 13 ------------- .../GravatarUI/ProfileView/BaseProfileView.swift | 2 +- .../ProfileView/ProfileViewConfiguration.swift | 11 ----------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/Sources/Gravatar/Network/Services/AvatarService.swift b/Sources/Gravatar/Network/Services/AvatarService.swift index cb273ff9..9db2d0a3 100644 --- a/Sources/Gravatar/Network/Services/AvatarService.swift +++ b/Sources/Gravatar/Network/Services/AvatarService.swift @@ -49,19 +49,6 @@ public struct AvatarService: Sendable { return try await imageDownloader.fetchImage(with: gravatarURL, forceRefresh: options.forceRefresh, processingMethod: options.processingMethod) } - /// Uploads an image to be used as the user's Gravatar profile image, and returns the `URLResponse` of the network tasks asynchronously. Throws - /// ``ImageUploadError``. - /// - Parameters: - /// - image: The image to be uploaded. - /// - email: An`Email` object - /// - accessToken: The authentication token for the user. This is a WordPress.com OAuth2 access token. - /// - Returns: An asynchronously-delivered `URLResponse` instance, containing the response of the upload network task. - @discardableResult - @available(*, deprecated, renamed: "upload(_:accessToken:)") - public func upload(_ image: UIImage, email: Email, accessToken: String) async throws -> URLResponse { - try await imageUploader.uploadImage(image, accessToken: accessToken, additionalHTTPHeaders: nil).response - } - /// Uploads an image to be used as the user's Gravatar profile image, and returns the `URLResponse` of the network tasks asynchronously. Throws /// ``ImageUploadError``. /// - Parameters: diff --git a/Sources/GravatarUI/ProfileView/BaseProfileView.swift b/Sources/GravatarUI/ProfileView/BaseProfileView.swift index 2e03d45e..e0b5bb72 100644 --- a/Sources/GravatarUI/ProfileView/BaseProfileView.swift +++ b/Sources/GravatarUI/ProfileView/BaseProfileView.swift @@ -423,7 +423,7 @@ open class BaseProfileView: UIView, UIContentView { private func loadAvatar(with config: ProfileViewConfiguration) { loadAvatar( - with: config.avatarIdentifier ?? config.avatarID, + with: config.avatarIdentifier, placeholder: config.avatarConfiguration.placeholder, rating: config.avatarConfiguration.rating, defaultAvatarOption: config.avatarConfiguration.defaultAvatarOption, diff --git a/Sources/GravatarUI/ProfileView/ProfileViewConfiguration.swift b/Sources/GravatarUI/ProfileView/ProfileViewConfiguration.swift index c291d191..04cdc4b8 100644 --- a/Sources/GravatarUI/ProfileView/ProfileViewConfiguration.swift +++ b/Sources/GravatarUI/ProfileView/ProfileViewConfiguration.swift @@ -23,17 +23,6 @@ public struct ProfileViewConfiguration: UIContentConfiguration { public var summaryModel: ProfileSummaryModel? /// The style for the profile view. public let profileStyle: Style - /// The identifier for the avatar image to be loaded in the profile view. - @available( - *, - deprecated, - renamed: "avatarIdentifier", - message: "Set `avatarIdentifier` explicitly and don't use the model's avatarIdentifier. It's because the `ProfileModel.avatarIdentifier` always refers to the primary email's avatar even if we query the profile with a secondary email." - ) - var avatarID: AvatarIdentifier? { - model?.avatarIdentifier ?? summaryModel?.avatarIdentifier - } - /// The identifier for the avatar image to be loaded in the profile view. public var avatarIdentifier: AvatarIdentifier? /// The palette to be used to style the view. From 01bc502bfb798e346673ff08681805d68fc2b178 Mon Sep 17 00:00:00 2001 From: etoledom Date: Wed, 9 Oct 2024 15:28:39 +0200 Subject: [PATCH 06/15] Fix RemoteSVGView crash (#479) --- .../ProfileView/AccountIconWebView/RemoteSVGView.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/GravatarUI/ProfileView/AccountIconWebView/RemoteSVGView.swift b/Sources/GravatarUI/ProfileView/AccountIconWebView/RemoteSVGView.swift index 7628a2fd..960a7bf7 100644 --- a/Sources/GravatarUI/ProfileView/AccountIconWebView/RemoteSVGView.swift +++ b/Sources/GravatarUI/ProfileView/AccountIconWebView/RemoteSVGView.swift @@ -20,11 +20,6 @@ class RemoteSVGButton: UIControl, WKNavigationDelegate { private var iconURL: URL? private static let cache: NSCache = .init() - init(iconSize: CGSize) { - self.iconSize = iconSize - super.init() - } - private lazy var webView: WKWebView = { let webView = WKWebView() webView.translatesAutoresizingMaskIntoConstraints = false From 800b67fa1213d47a4ca6267716e2fcf38d341503 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 9 Oct 2024 16:43:58 +0300 Subject: [PATCH 07/15] QE: Add empty check for the profile card field (#480) --- .../SwiftUI/AvatarPicker/AvatarPickerProfileView.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerProfileView.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerProfileView.swift index 0c93adef..b829b4d3 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerProfileView.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerProfileView.swift @@ -29,14 +29,17 @@ struct AvatarPickerProfileView: View { Text(model.displayName) .font(.title3) .fontWeight(.bold) - Text(model.location) - .font(.footnote) - .foregroundColor(Color(UIColor.secondaryLabel)) + if !model.location.isEmpty { + Text(model.location) + .font(.footnote) + .foregroundColor(Color(UIColor.secondaryLabel)) + } Button(Localized.viewProfileButtonTitle) { viewProfileAction?() } .font(.footnote) .foregroundColor(Color(UIColor.label)) + .padding(.init(top: .DS.Padding.half, leading: 0, bottom: 0, trailing: 0)) } } else { emptyViews() From dbd990df64a62dba6817822760e8a43eec165408 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 9 Oct 2024 19:22:57 +0300 Subject: [PATCH 08/15] Make HTTPClient internal (#482) --- .../Network/Services/AvatarService.swift | 25 ++++++------------- .../CheckTokenAuthorizationService.swift | 8 ++---- .../Network/Services/HTTPClient.swift | 2 +- .../Services/ImageDownloadService.swift | 20 ++++++--------- .../Network/Services/ImageUploadService.swift | 7 +++--- .../Network/Services/ProfileService.swift | 10 +++++--- .../Gravatar/Network/URLSessionProtocol.swift | 9 +++---- Sources/TestHelpers/HTTPClientMock.swift | 19 -------------- .../ImageDownloadService+Extension.swift | 5 ++-- Tests/GravatarTests/AvatarServiceTests.swift | 2 +- .../ImageDownloadServiceTests.swift | 5 ++-- Tests/GravatarTests/ProfileServiceTests.swift | 10 ++++---- Tests/GravatarUITests/OAuthSessionTests.swift | 2 +- .../ProfileViewModelTests.swift | 8 +++--- 14 files changed, 44 insertions(+), 88 deletions(-) delete mode 100644 Sources/TestHelpers/HTTPClientMock.swift diff --git a/Sources/Gravatar/Network/Services/AvatarService.swift b/Sources/Gravatar/Network/Services/AvatarService.swift index 9db2d0a3..5030253d 100644 --- a/Sources/Gravatar/Network/Services/AvatarService.swift +++ b/Sources/Gravatar/Network/Services/AvatarService.swift @@ -8,28 +8,19 @@ public struct AvatarService: Sendable { private let imageDownloader: ImageDownloader private let imageUploader: ImageUploader - /// Creates a new `AvatarService` - /// - /// Optionally, you can pass a custom type conforming to ``HTTPClient`` to gain control over networking tasks. - /// Similarly, you can pass a custom type conforming to ``ImageCaching`` to use your custom caching system. - /// - Parameters: - /// - client: A type which will perform basic networking operations. - /// - cache: A type which will perform image caching operations. - public init(client: HTTPClient, cache: ImageCaching? = nil) { - self.imageDownloader = ImageDownloadService(client: client, cache: cache) - self.imageUploader = ImageUploadService(client: client) - } - /// Creates a new `AvatarService` /// /// Optionally, you can pass a custom type conforming to ``URLSessionProtocol``. /// Similarly, you can pass a custom type conforming to ``ImageCaching`` to use your custom caching system. /// - Parameters: - /// - session: A type which will perform basic networking operations. By default, a properly configured URLSession instance will be used. - /// - cache: A type which will perform image caching operations. - public init(session: URLSessionProtocol? = nil, cache: ImageCaching? = nil) { - let client = URLSessionHTTPClient(urlSession: session) - self.init(client: client, cache: cache) + /// - urlSession: Manages the network tasks. It can be a [URLSession] or any other type that conforms to ``URLSessionProtocol``. + /// If not provided, a properly configured [URLSession] is used. + /// - cache: An image cache of type ``ImageCaching``. If not provided, it defaults to SDK's in-memory cache. + /// + /// [URLSession]: https://developer.apple.com/documentation/foundation/urlsession + public init(urlSession: URLSessionProtocol? = nil, cache: ImageCaching? = nil) { + self.imageDownloader = ImageDownloadService(urlSession: urlSession, cache: cache) + self.imageUploader = ImageUploadService(urlSession: urlSession) } /// Fetches a Gravatar user profile image using an `AvatarId`, and delivers the image asynchronously. See also: ``ImageDownloadService`` to diff --git a/Sources/Gravatar/Network/Services/CheckTokenAuthorizationService.swift b/Sources/Gravatar/Network/Services/CheckTokenAuthorizationService.swift index e9fd8a92..3f05b2d1 100644 --- a/Sources/Gravatar/Network/Services/CheckTokenAuthorizationService.swift +++ b/Sources/Gravatar/Network/Services/CheckTokenAuthorizationService.swift @@ -3,12 +3,8 @@ import Foundation package struct CheckTokenAuthorizationService: Sendable { private let client: HTTPClient - package init(session: URLSession? = nil) { - if let session { - self.client = URLSessionHTTPClient(urlSession: session) - } else { - self.client = URLSessionHTTPClient() - } + package init(session: URLSessionProtocol? = nil) { + self.client = URLSessionHTTPClient(urlSession: session) } /// Checks if the given access token is authorized to make changes to this Gravatar account. diff --git a/Sources/Gravatar/Network/Services/HTTPClient.swift b/Sources/Gravatar/Network/Services/HTTPClient.swift index dd338213..c652409e 100644 --- a/Sources/Gravatar/Network/Services/HTTPClient.swift +++ b/Sources/Gravatar/Network/Services/HTTPClient.swift @@ -4,7 +4,7 @@ import Foundation /// /// You can provide your own type conforming to this protocol to gain control over all networking operations performed internally by this SDK. /// For more info, see ``AvatarService/init(client:cache:)`` and ``ProfileService/init(client:)``. -public protocol HTTPClient: Sendable { +protocol HTTPClient: Sendable { /// Performs a data request using the information provided, and delivers the result asynchronously. /// - Parameter request: A URL request object that provides request-specific information such as the URL and cache policy. /// - Returns: An asynchronously-delivered tuple that contains the URL contents as a Data instance, and a HTTPURLResponse. diff --git a/Sources/Gravatar/Network/Services/ImageDownloadService.swift b/Sources/Gravatar/Network/Services/ImageDownloadService.swift index 1d85e1fe..57947bcf 100644 --- a/Sources/Gravatar/Network/Services/ImageDownloadService.swift +++ b/Sources/Gravatar/Network/Services/ImageDownloadService.swift @@ -2,25 +2,19 @@ import UIKit /// A service to perform image downloading. /// -/// This is the default type which implements ``ImageDownloader``.. -/// Unless specified otherwise, `ImageDownloadService` will use a `URLSession` based `HTTPClient`, and a in-memory image cache. +/// This is the default type which implements ``ImageDownloader``. public actor ImageDownloadService: ImageDownloader, Sendable { private let client: HTTPClient let imageCache: ImageCaching /// Creates a new `ImageDownloadService` - /// - /// Optionally, you can pass a custom type conforming to ``HTTPClient`` to gain control over networking tasks. - /// Similarly, you can pass a custom type conforming to ``ImageCaching`` to use your custom caching system. /// - Parameters: - /// - client: A type which will perform basic networking operations. - /// - cache: A type which will perform image caching operations. - public init(client: HTTPClient? = nil, cache: ImageCaching? = nil) { - self.client = client ?? URLSessionHTTPClient() - self.imageCache = cache ?? ImageCache.shared - } - - public init(urlSession: URLSession, cache: ImageCaching? = nil) { + /// - urlSession: Manages the network tasks. It can be a [URLSession] or any other type that conforms to ``URLSessionProtocol``. + /// If not provided, a properly configured [URLSession] is used. + /// - cache: An image cache of type ``ImageCaching``. If not provided, it defaults to SDK's in-memory cache. + /// + /// [URLSession]: https://developer.apple.com/documentation/foundation/urlsession + public init(urlSession: URLSessionProtocol? = nil, cache: ImageCaching? = nil) { self.client = URLSessionHTTPClient(urlSession: urlSession) self.imageCache = cache ?? ImageCache.shared } diff --git a/Sources/Gravatar/Network/Services/ImageUploadService.swift b/Sources/Gravatar/Network/Services/ImageUploadService.swift index 5c7c8e37..37ea184e 100644 --- a/Sources/Gravatar/Network/Services/ImageUploadService.swift +++ b/Sources/Gravatar/Network/Services/ImageUploadService.swift @@ -3,13 +3,12 @@ import UIKit /// A service to perform uploading images to Gravatar. /// -/// This is the default type which implements ``ImageUploader``.. -/// Unless specified otherwise, `ImageUploadService` will use a `URLSession` based `HTTPClient`. +/// This is the default type which implements ``ImageUploader``. struct ImageUploadService: ImageUploader { private let client: HTTPClient - init(client: HTTPClient? = nil) { - self.client = client ?? URLSessionHTTPClient() + init(urlSession: URLSessionProtocol? = nil) { + self.client = URLSessionHTTPClient(urlSession: urlSession) } @discardableResult diff --git a/Sources/Gravatar/Network/Services/ProfileService.swift b/Sources/Gravatar/Network/Services/ProfileService.swift index ee69b8f9..b2b9f018 100644 --- a/Sources/Gravatar/Network/Services/ProfileService.swift +++ b/Sources/Gravatar/Network/Services/ProfileService.swift @@ -16,11 +16,13 @@ public struct ProfileService: ProfileFetching, Sendable { private let client: HTTPClient /// Creates a new `ProfileService`. + /// - Parameters: + /// - urlSession: Manages the network tasks. It can be a [URLSession] or any other type that conforms to ``URLSessionProtocol``. + /// If not provided, a properly configured [URLSession] is used. /// - /// Optionally, you can pass a custom type conforming to ``HTTPClient`` to gain control over networking tasks. - /// - Parameter client: A type which will perform basic networking operations. - public init(client: HTTPClient? = nil) { - self.client = client ?? URLSessionHTTPClient() + /// [URLSession]: https://developer.apple.com/documentation/foundation/urlsession + public init(urlSession: URLSessionProtocol? = nil) { + self.client = URLSessionHTTPClient(urlSession: urlSession) } public func fetch(with profileID: ProfileIdentifier) async throws -> Profile { diff --git a/Sources/Gravatar/Network/URLSessionProtocol.swift b/Sources/Gravatar/Network/URLSessionProtocol.swift index b5f562f2..d989a798 100644 --- a/Sources/Gravatar/Network/URLSessionProtocol.swift +++ b/Sources/Gravatar/Network/URLSessionProtocol.swift @@ -1,12 +1,9 @@ import UIKit -/// Protocol for dependency injection purposes. `URLSession` conforms to this protocol. +/// Protocol for dependency injection purposes. [URLSession] conforms to this protocol. +/// +/// [URLSession]: https://developer.apple.com/documentation/foundation/urlsession public protocol URLSessionProtocol: Sendable { - func dataTask( - with request: URLRequest, - completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void - ) -> URLSessionDataTask - func data(for request: URLRequest) async throws -> (Data, URLResponse) func upload(for request: URLRequest, from bodyData: Data) async throws -> (Data, URLResponse) diff --git a/Sources/TestHelpers/HTTPClientMock.swift b/Sources/TestHelpers/HTTPClientMock.swift deleted file mode 100644 index 27af06aa..00000000 --- a/Sources/TestHelpers/HTTPClientMock.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation -import Gravatar - -package struct HTTPClientMock: HTTPClient { - private let session: URLSessionMock - - package init(session: URLSessionMock) { - self.session = session - } - - package func fetchData(with request: URLRequest) async throws -> (Data, HTTPURLResponse) { - await session.update(request: request) - return (session.returnData, session.response) - } - - package func uploadData(with request: URLRequest, data: Data) async throws -> (Data, HTTPURLResponse) { - (session.returnData, session.response) - } -} diff --git a/Sources/TestHelpers/ImageDownloadService+Extension.swift b/Sources/TestHelpers/ImageDownloadService+Extension.swift index c54d4f10..2a00447d 100644 --- a/Sources/TestHelpers/ImageDownloadService+Extension.swift +++ b/Sources/TestHelpers/ImageDownloadService+Extension.swift @@ -1,9 +1,8 @@ -@testable import Gravatar +import Gravatar extension ImageDownloadService { package static func mock(with session: URLSessionProtocol, cache: ImageCaching? = nil) -> ImageDownloadService { - let client = URLSessionHTTPClient(urlSession: session) - let service = ImageDownloadService(client: client, cache: cache) + let service = ImageDownloadService(urlSession: session, cache: cache) return service } } diff --git a/Tests/GravatarTests/AvatarServiceTests.swift b/Tests/GravatarTests/AvatarServiceTests.swift index f4ae3fe8..9c8f8501 100644 --- a/Tests/GravatarTests/AvatarServiceTests.swift +++ b/Tests/GravatarTests/AvatarServiceTests.swift @@ -131,6 +131,6 @@ final class AvatarServiceTests: XCTestCase { } private func avatarService(with session: URLSessionProtocol, cache: ImageCaching? = nil) -> AvatarService { - let service = AvatarService(session: session, cache: cache) + let service = AvatarService(urlSession: session, cache: cache) return service } diff --git a/Tests/GravatarTests/ImageDownloadServiceTests.swift b/Tests/GravatarTests/ImageDownloadServiceTests.swift index e360fbe0..0862d95f 100644 --- a/Tests/GravatarTests/ImageDownloadServiceTests.swift +++ b/Tests/GravatarTests/ImageDownloadServiceTests.swift @@ -1,4 +1,4 @@ -@testable import Gravatar +import Gravatar import TestHelpers import XCTest @@ -242,7 +242,6 @@ final class ImageDownloadServiceTests: XCTestCase { } private func imageDownloadService(with session: URLSessionProtocol, cache: ImageCaching? = nil) -> ImageDownloadService { - let client = URLSessionHTTPClient(urlSession: session) - let service = ImageDownloadService(client: client, cache: cache) + let service = ImageDownloadService(urlSession: session, cache: cache) return service } diff --git a/Tests/GravatarTests/ProfileServiceTests.swift b/Tests/GravatarTests/ProfileServiceTests.swift index dd3c9260..32cb55d7 100644 --- a/Tests/GravatarTests/ProfileServiceTests.swift +++ b/Tests/GravatarTests/ProfileServiceTests.swift @@ -1,4 +1,4 @@ -@testable import Gravatar +import Gravatar import TestHelpers import XCTest @@ -12,7 +12,7 @@ final class ProfileServiceTests: XCTestCase { return XCTFail("Could not create data") } let session = URLSessionMock(returnData: data, response: .successResponse()) - let service = ProfileService(client: HTTPClientMock(session: session)) + let service = ProfileService(urlSession: session) do { _ = try await service.fetch(with: .hashID("")) @@ -28,7 +28,7 @@ final class ProfileServiceTests: XCTestCase { return XCTFail("Could not create data") } let session = URLSessionMock(returnData: data, response: .successResponse()) - let service = ProfileService(client: HTTPClientMock(session: session)) + let service = ProfileService(urlSession: session) do { _ = try await service.fetch(with: .hashID("")) @@ -46,7 +46,7 @@ final class ProfileServiceTests: XCTestCase { return XCTFail("Could not create data") } let session = URLSessionMock(returnData: data, response: .errorResponse(code: 404)) - let service = ProfileService(client: URLSessionHTTPClient(urlSession: session)) + let service = ProfileService(urlSession: session) do { let _ = try await service.fetch(with: .hashID("")) @@ -66,7 +66,7 @@ final class ProfileServiceTests: XCTestCase { await Configuration.shared.configure(with: "somekey") let session = URLSessionMock(returnData: data, response: .successResponse()) - let service = ProfileService(client: HTTPClientMock(session: session)) + let service = ProfileService(urlSession: session) do { _ = try await service.fetch(with: .hashID("")) diff --git a/Tests/GravatarUITests/OAuthSessionTests.swift b/Tests/GravatarUITests/OAuthSessionTests.swift index 96bdd2b0..10e1975d 100644 --- a/Tests/GravatarUITests/OAuthSessionTests.swift +++ b/Tests/GravatarUITests/OAuthSessionTests.swift @@ -4,7 +4,7 @@ import XCTest final class OAuthSessionTests: XCTestCase { func testOAuth() { let mockSession = AuthenticationSessionMock(responseURL: URL(string: "some://url.com?code=someCode")!) - let oauth = OAuthSession(authenticationSession: mockSession) + let _ = OAuthSession(authenticationSession: mockSession) } } diff --git a/Tests/GravatarUITests/ProfileViewModelTests.swift b/Tests/GravatarUITests/ProfileViewModelTests.swift index 5d1f7919..7284558c 100644 --- a/Tests/GravatarUITests/ProfileViewModelTests.swift +++ b/Tests/GravatarUITests/ProfileViewModelTests.swift @@ -1,5 +1,5 @@ import Combine -@testable import Gravatar +import Gravatar import GravatarUI import SnapshotTesting import TestHelpers @@ -69,14 +69,12 @@ final class ProfileViewModelTests: XCTestCase { func successfulService() -> ProfileService { let session = URLSessionMock(returnData: jsonData, response: .successResponse()) - let client = URLSessionHTTPClient(urlSession: session) - return ProfileService(client: client) + return ProfileService(urlSession: session) } func failingService() -> ProfileService { let session = URLSessionMock(returnData: jsonData, response: .errorResponse(code: 404)) - let client = URLSessionHTTPClient(urlSession: session) - return ProfileService(client: client) + return ProfileService(urlSession: session) } } From 69c181c78e04d701569ab1d4016578c1aa5212f9 Mon Sep 17 00:00:00 2001 From: Andrew Montgomery Date: Wed, 9 Oct 2024 11:56:21 -0500 Subject: [PATCH 09/15] Check translation progress before downloading localizations --- fastlane/lanes/localization.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb index 25a97673..9d14e65c 100644 --- a/fastlane/lanes/localization.rb +++ b/fastlane/lanes/localization.rb @@ -64,6 +64,11 @@ SOURCES_TO_LOCALIZE.each do |source| next if source.gp_project_url.nil? + check_translation_progress( + glotpress_url: source.gp_project_url, + abort_on_violations: false + ) + ios_download_strings_files_from_glotpress( project_url: source.gp_project_url, locales: GLOTPRESS_TO_LPROJ_APP_LOCALE_CODES, From 389d8ef03c29ea32c16547e70abfe49d4c61191e Mon Sep 17 00:00:00 2001 From: Andrew Montgomery Date: Wed, 9 Oct 2024 12:14:35 -0500 Subject: [PATCH 10/15] Lint downloaded localizations --- .gitignore | 3 +++ fastlane/lanes/localization.rb | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index ac56bfb8..6b6b275c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ fastlane/README.md fastlane/report.xml fastlane/test_output +# SwiftGen (part of wpmreleasetoolkit) +vendor/swiftgen + # Other openapi-generator/ Demo/Demo/Secrets.swift diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb index 9d14e65c..8e87df83 100644 --- a/fastlane/lanes/localization.rb +++ b/fastlane/lanes/localization.rb @@ -75,6 +75,10 @@ download_dir: source.localizations_root ) + ios_lint_localizations( + input_dir: source.localizations_root + ) + next if skip_commit paths_to_commit << source.localizations_root From a191780df32dac307ead7a687c13570bd78b3a0d Mon Sep 17 00:00:00 2001 From: Andrew Montgomery Date: Wed, 9 Oct 2024 12:15:34 -0500 Subject: [PATCH 11/15] Fix rubocop warning --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 70a8ece2..94459d49 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -14,7 +14,7 @@ XCODEPROJ_PATH = File.join(PROJECT_ROOT_FOLDER, 'Gravatar-Demo.xcodeproj') DEMO_APPS_SOURCES_FOLDER = File.join(PROJECT_ROOT_FOLDER, 'Demo') XCCONFIG_PROTOTYPE_BUILD_SWIFTUI = File.join(DEMO_APPS_SOURCES_FOLDER, 'Gravatar-SwiftUI-Demo', 'Gravatar-SwiftUI-Demo.Release.xcconfig') XCCONFIG_PROTOTYPE_BUILD_UIKIT = File.join(DEMO_APPS_SOURCES_FOLDER, 'Gravatar-UIKit-Demo', 'Gravatar-UIKit-Demo.Release.xcconfig') -COMMON_XCARGS = ['-skipPackagePluginValidation'] # Allow SwiftPM plugins (e.g. swiftlint) called from Xcode to be used on CI without prior manual approval +COMMON_XCARGS = ['-skipPackagePluginValidation'].freeze # Allow SwiftPM plugins (e.g. swiftlint) called from Xcode to be used on CI without prior manual approval GITHUB_REPO = 'Automattic/Gravatar-SDK-iOS' GITHUB_URL = "https://github.com/#{GITHUB_REPO}".freeze From 3299a5dfc482a9edf387f77c60b307874aaa9db8 Mon Sep 17 00:00:00 2001 From: Andrew Montgomery Date: Wed, 9 Oct 2024 13:02:30 -0500 Subject: [PATCH 12/15] Remove missing `depends_on` key from `publish` step --- .buildkite/pipeline.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index ffd1b4cb..dccdfdef 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -47,7 +47,6 @@ steps: plugins: [$CI_TOOLKIT] depends_on: - "test" - - "test_demo" - "validate" - "lint" if: build.tag != null From 737d1f32205c4b10fbdf00b1018659aaca70e9df Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Thu, 10 Oct 2024 17:02:18 +0300 Subject: [PATCH 13/15] Force swiftui to update the view --- .../GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift index 6f3f9b5f..9ad9cb9f 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift @@ -151,6 +151,8 @@ class AvatarPickerViewModel: ObservableObject { func upload(_ image: UIImage, shouldSquareImage: Bool) async { guard let authToken else { return } + // objectWillChange is the only way that makes the UI update when the initial state is empty. + objectWillChange.send() let squareImage = shouldSquareImage ? image.squared() : image let localID = UUID().uuidString From ce446263c3e4b8c493b77b74156a07336830e40b Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Thu, 10 Oct 2024 17:03:05 +0300 Subject: [PATCH 14/15] Sync with server if there's no selected avatar --- .../SwiftUI/AvatarPicker/AvatarPickerViewModel.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift index 9ad9cb9f..a7ac46dc 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift @@ -185,6 +185,12 @@ class AvatarPickerViewModel: ObservableObject { let newModel = AvatarImageModel(id: avatar.id, source: .remote(url: avatar.url)) grid.replaceModel(withID: localID, with: newModel) + if selectedAvatarURL == nil { + // server side has some auto-selection logic that kicks in + // during the avatar upload if there's no selected avatar. + // so let's get synced. + refresh() + } } catch ImageUploadError.responseError(reason: let .invalidHTTPStatusCode(response, errorPayload)) where response.statusCode == HTTPStatus.badRequest.rawValue || response.statusCode == HTTPStatus.payloadTooLarge.rawValue { From 6962a405aca4222d58ed302ebc4ddb9734839499 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Thu, 10 Oct 2024 17:41:28 +0300 Subject: [PATCH 15/15] Update comment --- .../SwiftUI/AvatarPicker/AvatarPickerViewModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift index a7ac46dc..b02884c8 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/AvatarPickerViewModel.swift @@ -151,7 +151,8 @@ class AvatarPickerViewModel: ObservableObject { func upload(_ image: UIImage, shouldSquareImage: Bool) async { guard let authToken else { return } - // objectWillChange is the only way that makes the UI update when the initial state is empty. + // SwiftUI doesn't update the UI if the grid is empty. + // objectWillChange forces the update. objectWillChange.send() let squareImage = shouldSquareImage ? image.squared() : image let localID = UUID().uuidString