Skip to content

Commit

Permalink
chore: Implement UI display with manual scroll for Carousel View (RMC…
Browse files Browse the repository at this point in the history
…CX-7619)
  • Loading branch information
SoumenRautray committed Dec 9, 2024
1 parent bef15cc commit 71483d9
Show file tree
Hide file tree
Showing 17 changed files with 421 additions and 47 deletions.
95 changes: 86 additions & 9 deletions Sources/RInAppMessaging/CampaignDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ internal class CampaignDispatcher: CampaignDispatcherType, TaskSchedulable {
private let dispatchQueue = DispatchQueue(label: "IAM.CampaignDisplay", qos: .userInteractive)
private(set) var queuedCampaignIDs = [String]()
private(set) var isDispatching = false

private let urlCache: URLCache = {
// response must be <= 5% of mem/disk cap in order to committed to cache
let cache = URLCache(memoryCapacity: URLCache.shared.memoryCapacity,
diskCapacity: 100 * 1024 * 1024, // fits up to 5MB images
diskPath: "RInAppMessaging")
return cache
}()

weak var delegate: CampaignDispatcherDelegate?
var scheduledTask: DispatchWorkItem?
Expand All @@ -41,11 +49,7 @@ internal class CampaignDispatcher: CampaignDispatcherType, TaskSchedulable {
sessionConfig.timeoutIntervalForRequest = Constants.CampaignMessage.imageRequestTimeoutSeconds
sessionConfig.timeoutIntervalForResource = Constants.CampaignMessage.imageResourceTimeoutSeconds
sessionConfig.waitsForConnectivity = true
sessionConfig.urlCache = URLCache(
// response must be <= 5% of mem/disk cap in order to committed to cache
memoryCapacity: URLCache.shared.memoryCapacity,
diskCapacity: 100*1024*1024, // fits up to 5MB images
diskPath: "RInAppMessaging")
sessionConfig.urlCache = urlCache
httpSession = URLSession(configuration: sessionConfig)
}

Expand Down Expand Up @@ -109,7 +113,17 @@ internal class CampaignDispatcher: CampaignDispatcherType, TaskSchedulable {
self.dispatchNext()
return
}
self.displayCampaign(campaign, imageBlob: imgBlob)
if !(campaign.data.customJson?.carousel?.images?.isEmpty ?? true) {
if let carouselData = campaign.data.customJson?.carousel {
DispatchQueue.main.sync {
self.fetchImagesArray(from: carouselData) { images in
self.displayCampaign(campaign, imageBlob: imgBlob, carouselImages: images)
}
}
}
} else {
self.displayCampaign(campaign, imageBlob: imgBlob)
}
}
}
} else {
Expand All @@ -118,10 +132,9 @@ internal class CampaignDispatcher: CampaignDispatcherType, TaskSchedulable {
}
}

private func displayCampaign(_ campaign: Campaign, imageBlob: Data? = nil) {
private func displayCampaign(_ campaign: Campaign, imageBlob: Data? = nil, carouselImages: [UIImage?]? = nil) {
let campaignTitle = campaign.data.messagePayload.title

router.displayCampaign(campaign, associatedImageData: imageBlob, confirmation: {
router.displayCampaign(campaign, associatedImageData: imageBlob, carouselImages: carouselImages, confirmation: {
let contexts = campaign.contexts
guard let delegate = self.delegate, !contexts.isEmpty, !campaign.data.isTest else {
return true
Expand Down Expand Up @@ -167,4 +180,68 @@ internal class CampaignDispatcher: CampaignDispatcherType, TaskSchedulable {
completion(data)
}.resume()
}

func fetchImagesArray(from carousel: Carousel, completion: @escaping ([UIImage?]) -> Void) {
guard let imageDetails = carousel.images else {
completion([])
return
}

let filteredDetails = imageDetails
.sorted { $0.key < $1.key }
.prefix(5)
.map { $0 }

let dispatchGroup = DispatchGroup()
var images: [UIImage?] = Array(repeating: nil, count: filteredDetails.count)

for (index, detail) in filteredDetails.enumerated() {
guard let urlString = detail.value.imgUrl else {
images[index] = nil
continue
}

dispatchGroup.enter()
fetchCarouselImage(for: urlString) { image in
images[index] = image
dispatchGroup.leave()
}
}

dispatchGroup.notify(queue: .main) {
completion(images)
}
}

func fetchCarouselImage(for urlString: String, completion: @escaping (UIImage?) -> Void) {
guard let url = URL(string: urlString),
["jpg", "jpeg", "png"].contains(url.pathExtension.lowercased()) else {
completion(nil)
return
}

let request = URLRequest(url: url)

// Check cache
if let cachedResponse = self.urlCache.cachedResponse(for: request),
let cachedImage = UIImage(data: cachedResponse.data) {
completion(cachedImage)
return
}

URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let response = response,
error == nil,
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let image = UIImage(data: data) {
let cachedData = CachedURLResponse(response: response, data: data)
self.urlCache.storeCachedResponse(cachedData, for: request)
completion(image)
} else {
completion(nil)
}
}.resume()
}
}
1 change: 1 addition & 0 deletions Sources/RInAppMessaging/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ internal enum Constants {
enum CampaignMessage {
static let imageRequestTimeoutSeconds: TimeInterval = 20
static let imageResourceTimeoutSeconds: TimeInterval = 300
static let carouselThreshold: Int = 5
}

enum Request {
Expand Down
10 changes: 10 additions & 0 deletions Sources/RInAppMessaging/Models/Responses/CampaignDataModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ struct Carousel: Codable {
case images
}

init(images: [String: ImageDetails]?) {
self.images = images
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
images = try container.decodeIfPresent([String: ImageDetails].self, forKey: .images)
Expand All @@ -177,6 +181,12 @@ struct ImageDetails: Codable {
case altText
}

init(imgUrl: String?, link: String?, altText: String?) {
self.imgUrl = imgUrl
self.altText = altText
self.link = link
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
imgUrl = try container.decodeIfPresent(String.self, forKey: .imgUrl)
Expand Down
Loading

0 comments on commit 71483d9

Please sign in to comment.