Skip to content

Commit

Permalink
Divide code to separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
Hopsaheysa committed Jul 18, 2024
1 parent 662a919 commit 2c073ab
Show file tree
Hide file tree
Showing 6 changed files with 591 additions and 536 deletions.
16 changes: 14 additions & 2 deletions WultraMobileTokenSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@
EA6DDF1C29F807230011E234 /* OperationUIDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */; };
EA74F7B32C2561BB004340B9 /* WMTResultTexts.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA74F7B22C2561BB004340B9 /* WMTResultTexts.swift */; };
EA7A6E582B0E639800C1D4F4 /* WMTOperationDetailRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7A6E572B0E639800C1D4F4 /* WMTOperationDetailRequest.swift */; };
EA951B502C412E43006C76B5 /* WMTUserOperationVisualParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA951B4F2C412E43006C76B5 /* WMTUserOperationVisualParser.swift */; };
EA7EA22D2C494478000ECA41 /* WMTUserOperationVisualParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA951B4F2C412E43006C76B5 /* WMTUserOperationVisualParser.swift */; };
EA7EA22E2C49447B000ECA41 /* WMTUserOperationListVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA392BB62C4941CE00B6ADB7 /* WMTUserOperationListVisual.swift */; };
EA7EA2302C494546000ECA41 /* WMTUserOperationVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7EA22F2C494546000ECA41 /* WMTUserOperationVisual.swift */; };
EA7EA2322C49480E000ECA41 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7EA2312C49480E000ECA41 /* ImageDownloader.swift */; };
EA9795132C2C18450073E861 /* WMTTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9795122C2C18450073E861 /* WMTTemplates.swift */; };
EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */; };
EA9CE2C22AEBDB0D00FE4E35 /* WMTPACUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2C12AEBDB0D00FE4E35 /* WMTPACUtils.swift */; };
Expand Down Expand Up @@ -155,6 +158,7 @@
DCE660D024CEBECA00870E53 /* IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = "<group>"; };
DCE660D224CEF56400870E53 /* IntegrationProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationProxy.swift; sourceTree = "<group>"; };
EA294F3C29F6A07A00A0494E /* WMTOperationUIData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTOperationUIData.swift; sourceTree = "<group>"; };
EA392BB62C4941CE00B6ADB7 /* WMTUserOperationListVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTUserOperationListVisual.swift; sourceTree = "<group>"; };
EA44366929F9294600DDEC1C /* WMTPostApprovaScreenReview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovaScreenReview.swift; sourceTree = "<group>"; };
EA44366B29F9297100DDEC1C /* WMTPostApprovaScreenRedirect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovaScreenRedirect.swift; sourceTree = "<group>"; };
EA44366D29F9298100DDEC1C /* WMTPostApprovaScreenGeneric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovaScreenGeneric.swift; sourceTree = "<group>"; };
Expand All @@ -163,6 +167,8 @@
EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationUIDataTests.swift; sourceTree = "<group>"; };
EA74F7B22C2561BB004340B9 /* WMTResultTexts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTResultTexts.swift; sourceTree = "<group>"; };
EA7A6E572B0E639800C1D4F4 /* WMTOperationDetailRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTOperationDetailRequest.swift; sourceTree = "<group>"; };
EA7EA22F2C494546000ECA41 /* WMTUserOperationVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTUserOperationVisual.swift; sourceTree = "<group>"; };
EA7EA2312C49480E000ECA41 /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
EA951B4F2C412E43006C76B5 /* WMTUserOperationVisualParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTUserOperationVisualParser.swift; sourceTree = "<group>"; };
EA9795122C2C18450073E861 /* WMTTemplates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTTemplates.swift; sourceTree = "<group>"; };
EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTProximityCheck.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -465,6 +471,9 @@
isa = PBXGroup;
children = (
EA951B4F2C412E43006C76B5 /* WMTUserOperationVisualParser.swift */,
EA392BB62C4941CE00B6ADB7 /* WMTUserOperationListVisual.swift */,
EA7EA22F2C494546000ECA41 /* WMTUserOperationVisual.swift */,
EA7EA2312C49480E000ECA41 /* ImageDownloader.swift */,
);
path = TemplateParser;
sourceTree = "<group>";
Expand Down Expand Up @@ -619,10 +628,10 @@
buildActionMask = 2147483647;
files = (
DC81D1C9244F38DB00F80CD6 /* WMTPushEndpoints.swift in Sources */,
EA951B502C412E43006C76B5 /* WMTUserOperationVisualParser.swift in Sources */,
DC0268DF29965495000BB9FA /* WMTOperationListResponse.swift in Sources */,
DC8CB202244DCBE2009DDAA3 /* WMTOperations.swift in Sources */,
DC48803E292282FF00DB844B /* WMTInboxMessage.swift in Sources */,
EA7EA22E2C49447B000ECA41 /* WMTUserOperationListVisual.swift in Sources */,
DCC5CCB52449F8E9004679AC /* WMTOperationAttributeAmount.swift in Sources */,
DCC5CCD6244DBB7F004679AC /* WMTPushRegistrationData.swift in Sources */,
DC3D0B392480F886000DC4D9 /* WMTLocalOperation.swift in Sources */,
Expand All @@ -637,6 +646,7 @@
DC6E52D6259C964600FC25BE /* WMTOperationExpirationWatcher.swift in Sources */,
DCC5CCDA244DBBE2004679AC /* WMTRejectionData.swift in Sources */,
EA9795132C2C18450073E861 /* WMTTemplates.swift in Sources */,
EA7EA22D2C494478000ECA41 /* WMTUserOperationVisualParser.swift in Sources */,
DC48803F292282FF00DB844B /* WMTInboxMessageDetail.swift in Sources */,
EA6DDF0F29F8036B0011E234 /* WMTPreApprovalScreen.swift in Sources */,
DCAB7BC824580B4C0006989D /* WMTQROperationParser.swift in Sources */,
Expand All @@ -663,8 +673,10 @@
DCC3420424E3DB310045D27D /* WMTPushParser.swift in Sources */,
BFEEB20529379C700047941D /* WMTInboxGetMessageDetail.swift in Sources */,
DCE5EAB026BD81150061861A /* WMTOperationHistoryEntry.swift in Sources */,
EA7EA2322C49480E000ECA41 /* ImageDownloader.swift in Sources */,
EACAF7B02A126B7D0021CA54 /* WMTJsonValue.swift in Sources */,
DCAB7BCA24580BAC0006989D /* WMTQROperation.swift in Sources */,
EA7EA2302C494546000ECA41 /* WMTUserOperationVisual.swift in Sources */,
DCC5CCBF2449F981004679AC /* WMTOperationAttributePartyInfo.swift in Sources */,
DC81D1CB244F451E00F80CD6 /* WMTPushImpl.swift in Sources */,
EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// Copyright 2024 Wultra s.r.o.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions
// and limitations under the License.
//

import UIKit

/// Simple image URL downloader with a simple cache implementation
internal class ImageDownloader {

public static let shared = ImageDownloader()

public class Callback {

fileprivate let callback: (UIImage?) -> Void
fileprivate(set) var canceled = false

public init(callback: @escaping (UIImage?) -> Void) {
self.callback = callback
}

public func cancel() {
canceled = true
}

fileprivate func setResult(_ image: UIImage?) {
guard canceled == false else {
return
}
callback(image)
}
}

private var cache: NSCache<NSString, UIImage>

private var waitingList = [URL: [Callback]]()
private let lock = WMTLock()

public init(byteCacheSize: Int = 20_000_000) { // ~20 mb
cache = NSCache()
cache.totalCostLimit = byteCacheSize
}

/// Downloads image for given URL
/// - Parameters:
/// - url: URL where the image is
/// - allowCache: If the image can be cached or loaded from cache
/// - delayError: Should error be delayed? For example, when the URL does not exist (404), it will fail in almost instant and it's better
/// for the UI to "simulate communication".
/// - completion: Completion with nil on error. Always invoked on main thread
public func downloadImage(at url: URL, allowCache: Bool = true, delayError: Bool = true, _ callback: Callback) {

if allowCache, let cached = cache.object(forKey: NSString(string: url.absoluteString)) {
callback.setResult(cached)
return
}

lock.synchronized {
if var list = waitingList[url] {
list.append(callback)
waitingList[url] = list
} else {
waitingList[url] = [callback]
}
}

DispatchQueue.global().async { [weak self] in

let started = Date()
let data = try? Data(contentsOf: url)
let elapsed = Date().timeIntervalSince(started)
let delay = delayError && data == nil && elapsed < 0.8

DispatchQueue.main.asyncAfter(deadline: .now() + (delay ? 0.7 : 0) ) {

guard let self else {
return
}

self.lock.synchronized {
if let data, let image = UIImage(data: data) {
if allowCache {
self.cache.setObject(image, forKey: NSString(string: url.absoluteString), cost: data.count)
}
self.waitingList[url]?.forEach { $0.setResult(image) }
} else {
self.waitingList[url]?.forEach { $0.setResult(nil) }
}

self.waitingList.removeValue(forKey: url)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//
// Copyright 2024 Wultra s.r.o.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions
// and limitations under the License.
//

import UIKit

public struct WMTUserOperationListVisual {
public let header: String?
public let title: String?
public let message: String?
public let style: String?
public let thumbnailImageURL: URL?
public let template: WMTTemplates.ListTemplate?

private let downloader = ImageDownloader.shared

public init(
header: String? = nil,
title: String? = nil,
message: String? = nil,
style: String? = nil,
thumbnailImageURL: URL? = nil,
template: WMTTemplates.ListTemplate? = nil
) {
self.header = header
self.title = title
self.message = message
self.style = style
self.thumbnailImageURL = thumbnailImageURL
self.template = template
}

public func downloadThumbnail(callback: @escaping (UIImage?) -> Void) {

guard let url = thumbnailImageURL else {
callback(nil)
return
}

downloader.downloadImage(
at: url,
ImageDownloader.Callback { img in
if let img {
callback(img)
} else {
callAgain(callback: callback)
}
}
)
}

public func callAgain(callback: @escaping (UIImage?) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
self.downloadThumbnail(callback: callback)
}
}
}

// MARK: WMTUserOperation List Visual preparation extension
extension WMTUserOperation {

internal func prepareVisualListDetail() -> WMTUserOperationListVisual {
let listTemplate = self.ui?.templates?.list
let attributes = self.formData.attributes
let headerAtrr = listTemplate?.header?.replacePlaceholders(from: attributes)

var title: String? {
if let titleAttr = listTemplate?.title?.replacePlaceholders(from: attributes) {
return titleAttr
}

if !self.formData.message.isEmpty {
return self.formData.title
}

return nil
}

var message: String? {
if let messageAttr = listTemplate?.message?.replacePlaceholders(from: attributes) {
return messageAttr
}

if !self.formData.message.isEmpty {
return self.formData.message
}

return nil
}

var imageUrl: URL? {
if let imgAttr = listTemplate?.image,
let imgAttrCell = self.formData.attributes
.compactMap({ $0 as? WMTOperationAttributeImage })
.first(where: { $0.label.id == imgAttr }) {
return URL(string: imgAttrCell.thumbnailUrl)
}

if let imgAttrCell = self.formData.attributes
.compactMap({ $0 as? WMTOperationAttributeImage })
.first {
return URL(string: imgAttrCell.thumbnailUrl)
}

return nil
}

return WMTUserOperationListVisual(
header: headerAtrr,
title: title,
message: message,
style: self.ui?.templates?.list?.style,
thumbnailImageURL: imageUrl,
template: listTemplate
)
}
}

// MARK: Helpers

internal extension String {

// Function to replace placeholders in the template with actual values
func replacePlaceholders(from attributes: [WMTOperationAttribute]) -> String? {
var result = self

if let placeholders = extractPlaceholders() {
for placeholder in placeholders {
if let value = findAttributeValue(for: placeholder, from: attributes) {
result = result.replacingOccurrences(of: "${\(placeholder)}", with: value)
} else {
D.debug("Placeholder Attribute: \(placeholder) in WMTUserAttributes not found.")
return nil
}
}
}
return result
}

private func extractPlaceholders() -> [String]? {
do {
let regex = try NSRegularExpression(pattern: "\\$\\{(.*?)\\}", options: [])
let matches = regex.matches(in: self, options: [], range: NSRange(location: 0, length: self.count))

var attributeIds: [String] = []
for match in matches {
if let range = Range(match.range(at: 1), in: self) {
let key = String(self[range])
attributeIds.append(key)
}
}
return attributeIds
} catch {
D.warning("Error creating NSRegularExpression: \(error) in WMTListParser.")
return nil
}
}

private func findAttributeValue(for attributeId: String, from attributes: [WMTOperationAttribute]) -> String? {
for attribute in attributes where attribute.label.id == attributeId {
switch attribute.type {
case .amount:
guard let attr = attribute as? WMTOperationAttributeAmount else { return nil }
return attr.valueFormatted ?? "\(attr.amountFormatted) \(attr.currencyFormatted)"

case .amountConversion:
guard let attr = attribute as? WMTOperationAttributeAmountConversion else { return nil }
if let sourceValue = attr.source.valueFormatted,
let targetValue = attr.target.valueFormatted {
return "\(sourceValue)\(targetValue)"
} else {
let source = "\(attr.source.amountFormatted) \(attr.source.currencyFormatted)"
let target = "\(attr.target.amountFormatted) \(attr.target.currencyFormatted)"
return "\(source)\(target)"
}

case .keyValue:
guard let attr = attribute as? WMTOperationAttributeKeyValue else { return nil }
return attr.value
case .note:
guard let attr = attribute as? WMTOperationAttributeNote else { return nil }
return attr.note
case .heading:
guard let attr = attribute as? WMTOperationAttributeHeading else { return nil }
return attr.label.value
case .partyInfo, .image, .unknown:
return nil
}
}
return nil
}
}
Loading

0 comments on commit 2c073ab

Please sign in to comment.