Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This workflow will build a Swift project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift

name: Swift

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

jobs:
build:

runs-on: macos-latest

steps:
- uses: actions/checkout@v4
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
6 changes: 3 additions & 3 deletions BVSwift/BVConversations/Model/BVPhoto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public struct BVPhoto: BVSubmissionable {
while BVPhoto.MaxImageBytes <= data.count {

if let lossy: Data =
workingImage.jpegData(compressionQuality: 9.0) {
workingImage.jpegData(compressionQuality: 0.9) {
data = lossy
}

Expand Down Expand Up @@ -135,9 +135,9 @@ public struct BVPhotoSize: Codable {
}

extension BVPhoto {
public init(_ image: UIImage, _ caption: String? = nil) {
public init(_ image: UIImage, _ caption: String? = nil, _ contentType: ContentType? = nil) {
self.caption = caption
self.contentType = nil
self.contentType = contentType
self.image = image
self.photoId = nil
self.photoSizesArray = nil
Expand Down
93 changes: 88 additions & 5 deletions BVSwift/BVConversations/Model/BVProgressiveReviewFields.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

import Foundation

/// - Adds `set(name:value:)` / `get(name:)` for dynamic fields (e.g., "photourl_1")
public struct BVProgressiveReviewFields: BVAuxiliaryable {

// MARK: Typed fields (keep your existing ones)
public var rating: Int?
public var title: String?
public var reviewtext: String?
Expand All @@ -18,8 +19,36 @@ public struct BVProgressiveReviewFields: BVAuxiliaryable {
public var sendEmailAlert: Bool?
public var hostedAuthenticationEmail: String?
public var hostedAuthenticationCallbackurl: String?


/// Dynamic/additional fields merged into payload on encode.
/// e.g., "photourl_1", "contextdatavalue_<id>", custom form fields, etc.
private var additional: [String: String] = [:]

// MARK: Init
public init() {}

// MARK: Dynamic setters/getters
/// Set an arbitrary field by name (e.g., "photourl_1")
public mutating func set(name: String, value: String) {
additional[name] = value
}

/// Read an arbitrary field
public func get(name: String) -> String? {
additional[name]
}

/// Remove an arbitrary field
public mutating func remove(name: String) {
additional.removeValue(forKey: name)
}

/// Inspect all additional fields (read-only)
public var allAdditional: [String: String] { additional }
}

// MARK: - Custom Coding (flatten typed + dynamic)
extension BVProgressiveReviewFields: Codable {
private enum CodingKeys: String, CodingKey {
case rating = "rating"
case title = "title"
Expand All @@ -30,7 +59,61 @@ public struct BVProgressiveReviewFields: BVAuxiliaryable {
case hostedAuthenticationEmail = "hostedauthentication_authenticationemail"
case hostedAuthenticationCallbackurl = "hostedauthentication_callbackurl"
}

//To make accessable by CocoaPods
public init() {}

public func encode(to encoder: Encoder) throws {
// Encode typed keys first using their API field names…
var keyed = encoder.container(keyedBy: CodingKeys.self)
try keyed.encodeIfPresent(rating, forKey: .rating)
try keyed.encodeIfPresent(title, forKey: .title)
try keyed.encodeIfPresent(reviewtext, forKey: .reviewtext)
try keyed.encodeIfPresent(agreedToTerms, forKey: .agreedToTerms)
try keyed.encodeIfPresent(isRecommended, forKey: .isRecommended)
try keyed.encodeIfPresent(sendEmailAlert, forKey: .sendEmailAlert)
try keyed.encodeIfPresent(hostedAuthenticationEmail, forKey: .hostedAuthenticationEmail)
try keyed.encodeIfPresent(hostedAuthenticationCallbackurl, forKey: .hostedAuthenticationCallbackurl)

// …then merge dynamic keys
var dyn = encoder.container(keyedBy: DynamicCodingKeys.self)
for (k, v) in additional {
try dyn.encode(v, forKey: .init(stringValue: k)!)
}
}

public init(from decoder: Decoder) throws {
// Decode known keys
let keyed = try decoder.container(keyedBy: CodingKeys.self)
rating = try keyed.decodeIfPresent(Int.self, forKey: .rating)
title = try keyed.decodeIfPresent(String.self, forKey: .title)
reviewtext = try keyed.decodeIfPresent(String.self, forKey: .reviewtext)
agreedToTerms = try keyed.decodeIfPresent(Bool.self, forKey: .agreedToTerms)
isRecommended = try keyed.decodeIfPresent(Bool.self, forKey: .isRecommended)
sendEmailAlert = try keyed.decodeIfPresent(Bool.self, forKey: .sendEmailAlert)
hostedAuthenticationEmail = try keyed.decodeIfPresent(String.self, forKey: .hostedAuthenticationEmail)
hostedAuthenticationCallbackurl = try keyed.decodeIfPresent(String.self, forKey: .hostedAuthenticationCallbackurl)

// Collect everything else as strings into `additional`
additional = [:]
let dyn = try decoder.container(keyedBy: DynamicCodingKeys.self)
for key in dyn.allKeys {
// Skip any keys already mapped to typed fields
if CodingKeys(rawValue: key.stringValue) != nil { continue }

// Be forgiving: try String first, then Int/Bool -> String
if let s = try? dyn.decode(String.self, forKey: key) {
additional[key.stringValue] = s
} else if let i = try? dyn.decode(Int.self, forKey: key) {
additional[key.stringValue] = String(i)
} else if let b = try? dyn.decode(Bool.self, forKey: key) {
additional[key.stringValue] = b ? "true" : "false"
}
}
}

private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { nil }
init?(intValue: Int) { nil }
}
}