From 14d86ee0849dab5df0f01005ec3ee624efffff75 Mon Sep 17 00:00:00 2001 From: Takeichi Kanzaki Cabrera Date: Tue, 29 Oct 2019 14:24:46 +0100 Subject: [PATCH] release version 2.2.1 --- AptoUISDK.podspec | 4 +- Example/Demo/Info.plist | 2 +- .../Manager/AptoPlatformTestDoubles.swift | 2 + .../ModelDataProvider/ModelDataProvider.swift | 4 +- .../ServiceLocator/ModuleLocatorFake.swift | 2 +- .../ExternalOAuthPresenterTest.swift | 1 + .../CardMonthlyStatsInteractorTest.swift | 64 +++++++++++++ .../CardMonthlyStatsPresenterTest.swift | 81 +++++++++++++++++ .../CardMonthlyStatsTestDoubles.swift | 26 ++++++ .../MonthlyStatementsListPresenterTest.swift | 2 + .../CardWaitListModuleTest.swift | 33 +++++-- .../ExternalOAuthContract.swift | 1 + .../ExternalOAuthModuleConfig.swift | 1 + .../ExternalOAuthPresenter.swift | 1 + .../ExternalOAuthViewControllerTheme2.swift | 21 +++++ .../SelectBalanceStoreModule.swift | 1 + Pod/Classes/ui/AptoPlatformUI.swift | 10 +-- .../CardMonthlyStatsContract.swift | 4 +- .../CardMonthlyStatsInteractor.swift | 8 ++ .../CardMonthlyStatsPresenter.swift | 24 +++++ .../FundingSourceSelectorModule.swift | 2 +- .../ManageShiftCardModule.swift | 89 ++++++++----------- ...ShiftCardTransactionDetailsPresenter.swift | 7 +- ...ansactionDetailsViewControllerTheme2.swift | 6 +- .../Cell/MonthlyStatementListCell.swift | 10 ++- .../MonthlyStatementsListContract.swift | 1 + .../MonthlyStatementsListPresenter.swift | 2 + .../MonthlyStatementsListViewController.swift | 31 ++++++- ...onthlyStatementsReportViewController.swift | 1 + .../WaitListModule/CardWaitListModule.swift | 22 +++-- .../ModuleLocator/ModuleLocator.swift | 4 +- .../ModuleLocator/ModuleLocatorProtocol.swift | 2 +- .../Components/ContentPresenterView.swift | 4 +- .../Components/UIPinEntryTextField.swift | 1 + Pod/Classes/ui/UIKit/Components/UIView.swift | 21 +++-- .../ui/UIKit/Components/UIWebView.swift | 15 ---- .../ShowGenericMessageViewController.swift | 23 ++++- .../View/WebBrowserViewControllerTheme1.swift | 39 ++++---- .../View/WebBrowserViewControllerTheme2.swift | 39 ++++---- Pod/Localization/en.lproj/Localizable.strings | 2 + 40 files changed, 461 insertions(+), 152 deletions(-) delete mode 100644 Pod/Classes/ui/UIKit/Components/UIWebView.swift diff --git a/AptoUISDK.podspec b/AptoUISDK.podspec index c0770f9..d8f59ec 100644 --- a/AptoUISDK.podspec +++ b/AptoUISDK.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "AptoUISDK" - s.version = "2.2.0" + s.version = "2.2.1" s.summary = "The Apto UI platform iOS SDK." s.description = <<-DESC Apto iOS UI SDK @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.homepage = "https://github.com/ShiftFinancial/apto-ui-sdk-ios.git" s.license = { :type => 'MIT', :file => 'LICENSE' } s.authors = { "Ivan Oliver" => "ivan@aptopayments.com", "Takeichi Kanzaki" => "takeichi@aptopayments.com" } - s.source = { :git => "https://github.com/ShiftFinancial/apto-ui-sdk-ios.git", :tag => "2.2.0" } + s.source = { :git => "https://github.com/ShiftFinancial/apto-ui-sdk-ios.git", :tag => "2.2.1" } s.platform = :ios s.ios.deployment_target = '10.0' diff --git a/Example/Demo/Info.plist b/Example/Demo/Info.plist index 397b875..32962a9 100644 --- a/Example/Demo/Info.plist +++ b/Example/Demo/Info.plist @@ -3,7 +3,7 @@ BuildDate - 2019-10-14T10:49:35Z + 2019-10-29T13:20:19Z BuildType Local CFBundleDevelopmentRegion diff --git a/Example/UnitTests/common/Manager/AptoPlatformTestDoubles.swift b/Example/UnitTests/common/Manager/AptoPlatformTestDoubles.swift index 4038747..4555332 100644 --- a/Example/UnitTests/common/Manager/AptoPlatformTestDoubles.swift +++ b/Example/UnitTests/common/Manager/AptoPlatformTestDoubles.swift @@ -85,8 +85,10 @@ class AptoPlatformFake: AptoPlatformProtocol { var nextIsFeatureEnabledResult = true private(set) var isFeatureEnabledCalled = false + private(set) var lastIsFeatureEnabledKey: FeatureKey? func isFeatureEnabled(_ featureKey: FeatureKey) -> Bool { isFeatureEnabledCalled = true + lastIsFeatureEnabledKey = featureKey return nextIsFeatureEnabledResult } diff --git a/Example/UnitTests/common/ModelDataProvider/ModelDataProvider.swift b/Example/UnitTests/common/ModelDataProvider/ModelDataProvider.swift index 78b1f5b..f035142 100644 --- a/Example/UnitTests/common/ModelDataProvider/ModelDataProvider.swift +++ b/Example/UnitTests/common/ModelDataProvider/ModelDataProvider.swift @@ -29,7 +29,8 @@ class ModelDataProvider { lazy var amountRangeConfiguration = AmountRangeConfiguration(min: 0, max: 1000, def: 100, inc: 100) lazy var workflowAction: WorkflowAction = { - let configuration = SelectBalanceStoreActionConfiguration(allowedBalanceTypes: [balanceType]) + let configuration = SelectBalanceStoreActionConfiguration(allowedBalanceTypes: [balanceType], + assetUrl: nil) return WorkflowAction(actionId: nil, name: nil, order: nil, @@ -452,6 +453,7 @@ class ModelDataProvider { callToAction: "callToAction", newUserAction: "newUserAction", allowedBalanceTypes: [balanceType], + assetUrl: url, oauthErrorMessageKeys: nil) lazy var custodian = Custodian(custodianType: "custodian", name: "Custodian") diff --git a/Example/UnitTests/common/ServiceLocator/ModuleLocatorFake.swift b/Example/UnitTests/common/ServiceLocator/ModuleLocatorFake.swift index e8e3725..976ee84 100644 --- a/Example/UnitTests/common/ServiceLocator/ModuleLocatorFake.swift +++ b/Example/UnitTests/common/ServiceLocator/ModuleLocatorFake.swift @@ -113,7 +113,7 @@ class ModuleLocatorFake: ModuleLocatorProtocol { lazy var cardWaitListModuleSpy: CardWaitListModuleSpy = { return CardWaitListModuleSpy(serviceLocator: serviceLocator) }() - func cardWaitListModule(card: Card, cardProduct: CardProduct) -> CardWaitListModuleProtocol { + func cardWaitListModule(card: Card) -> CardWaitListModuleProtocol { return cardWaitListModuleSpy } diff --git a/Example/UnitTests/ledgeaccountskit/ExternalOAuthModule/ExternalOAuthPresenterTest.swift b/Example/UnitTests/ledgeaccountskit/ExternalOAuthModule/ExternalOAuthPresenterTest.swift index 9fddc9b..be35695 100644 --- a/Example/UnitTests/ledgeaccountskit/ExternalOAuthModule/ExternalOAuthPresenterTest.swift +++ b/Example/UnitTests/ledgeaccountskit/ExternalOAuthModule/ExternalOAuthPresenterTest.swift @@ -40,6 +40,7 @@ class ExternalOAuthPresenterTest: XCTestCase { XCTAssertNotNil(sut.viewModel.callToAction.value) XCTAssertNotNil(sut.viewModel.newUserAction.value) XCTAssertNotNil(sut.viewModel.allowedBalanceTypes.value) + XCTAssertNotNil(sut.viewModel.assetUrl) XCTAssertNil(sut.viewModel.error.value) } diff --git a/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsInteractorTest.swift b/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsInteractorTest.swift index 7b4057d..0a4bc44 100644 --- a/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsInteractorTest.swift +++ b/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsInteractorTest.swift @@ -87,4 +87,68 @@ class CardMonthlyStatsInteractorTest: XCTestCase { // Then XCTAssertEqual(date, returnedResult?.value?.date) } + + func testIsStatementFeatureEnabledCallPlatform() { + // When + sut.isStatementsFeatureEnabled { _ in } + + // Then + XCTAssertTrue(platform.isFeatureEnabledCalled) + XCTAssertEqual(FeatureKey.showMonthlyStatementsOption.rawValue, platform.lastIsFeatureEnabledKey?.rawValue) + } + + func testStatementsFeatureEnabledCallbackTrue() { + // Given + platform.nextIsFeatureEnabledResult = true + + // When + sut.isStatementsFeatureEnabled { isEnabled in + // Then + XCTAssertTrue(isEnabled) + } + } + + func testStatementsFeatureDisabledCallbackFalse() { + // Given + platform.nextIsFeatureEnabledResult = false + + // When + sut.isStatementsFeatureEnabled { isEnabled in + // Then + XCTAssertFalse(isEnabled) + } + } + + func testFetchStatementPeriodCallPlatform() { + // When + sut.fetchStatementsPeriod { _ in } + + // Then + XCTAssertTrue(platform.fetchMonthlyStatementsPeriodCalled) + } + + func testFetchStatementSucceedCallbackSuccess() { + // Given + platform.nextFetchMonthlyStatementsPeriodResult = .success(dataProvider.monthlyStatementsPeriod) + + // When + sut.fetchStatementsPeriod { [unowned self] result in + // Then + XCTAssertTrue(result.isSuccess) + XCTAssertEqual(self.dataProvider.monthlyStatementsPeriod, result.value) + } + + func testFetchStatementFailsCallbackFailure() { + // Given + let error = BackendError(code: .other) + platform.nextFetchMonthlyStatementsPeriodResult = .failure(error) + + // When + sut.fetchStatementsPeriod { result in + // Then + XCTAssertTrue(result.isFailure) + XCTAssertEqual(error, result.error) + } + } + } } diff --git a/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsPresenterTest.swift b/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsPresenterTest.swift index ce2373c..dd29aaf 100644 --- a/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsPresenterTest.swift +++ b/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsPresenterTest.swift @@ -8,6 +8,7 @@ import XCTest @testable import AptoSDK @testable import AptoUISDK +import Bond class CardMonthlyStatsPresenterTest: XCTestCase { var sut: CardMonthlyStatsPresenter! // swiftlint:disable:this implicitly_unwrapped_optional @@ -230,4 +231,84 @@ class CardMonthlyStatsPresenterTest: XCTestCase { XCTAssertTrue(router.showStatementReportCalled) XCTAssertEqual(Month(from: date), router.lastShowStatementReportMonth) } + + func testViewLoadedCheckIfStatementFeatureIsEnabled() { + // When + sut.viewLoaded() + + // Then + XCTAssertTrue(interactor.isStatementsFeatureEnabledCalled) + } + + func testStatementFeatureDisabledDoNotFetchPeriod() { + // Given + interactor.nextIsStatementsFeatureEnabledResult = false + + // When + sut.viewLoaded() + + // Then + XCTAssertFalse(interactor.fetchStatementsPeriodCalled) + } + + func testStatementFeatureEnabledFetchStatementsPeriod() { + // Given + givenStatementsFeatureEnabled() + + // When + sut.viewLoaded() + + // Then + XCTAssertTrue(interactor.fetchStatementsPeriodCalled) + } + + func testFetchStatementsPeriodSucceedCurrentMonthInPeriodMonthlyStatementAvailable() { + // Given + givenStatementPeriodSucceed() + + // When + sut.viewLoaded() + + // Then + XCTAssertTrue(sut.viewModel.monthlyStatementsAvailable.value) + } + + func testStatementsPeriodLoadedSelectedDateNotInPeriodMonthlyStatementNotAvailable() { + // Given + givenStatementPeriodSucceed() + sut.viewLoaded() + + // When + sut.dateSelected(Date.distantFuture) + + // Then + XCTAssertFalse(sut.viewModel.monthlyStatementsAvailable.value) + } + + func testStatementsPeriodLoadedSelectedDateInPeriodMonthlyStatementAvailable() { + // Given + givenStatementPeriodSucceed() + sut.viewLoaded() + sut.viewModel.monthlyStatementsAvailable.send(false) + + // When + sut.dateSelected(Date()) + + // Then + XCTAssertTrue(sut.viewModel.monthlyStatementsAvailable.value) + } + + // MARK: - Test helpers + private func givenStatementsFeatureEnabled() { + interactor.nextIsStatementsFeatureEnabledResult = true + } + + private func givenStatementPeriodSucceed() { + givenStatementsFeatureEnabled() + // swiftlint:disable force_unwrapping + let statementsPeriod = MonthlyStatementsPeriod(start: Month(from: Date().add(-2, units: .month)!), + end: Month(from: Date().add(2, units: .month)!)) + // swiftlint:enable force_unwrapping + interactor.nextFetchStatementsPeriodResult = .success(statementsPeriod) + } } diff --git a/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsTestDoubles.swift b/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsTestDoubles.swift index 02478c6..5feb995 100644 --- a/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsTestDoubles.swift +++ b/Example/UnitTests/shiftcardkit/CardMonthlyStats/CardMonthlyStatsTestDoubles.swift @@ -37,6 +37,16 @@ class CardMonthlyStatsInteractorSpy: CardMonthlyStatsInteractorProtocol { lastDateToFetchData = date fetchMonthlySpendingCallCounter += 1 } + + private(set) var isStatementsFeatureEnabledCalled = false + func isStatementsFeatureEnabled(callback: @escaping (_ isEnabled: Bool) -> Void) { + isStatementsFeatureEnabledCalled = true + } + + private(set) var fetchStatementsPeriodCalled = false + func fetchStatementsPeriod(callback: @escaping Result.Callback) { + fetchStatementsPeriodCalled = true + } } class CardMonthlyStatsInteractorFake: CardMonthlyStatsInteractorSpy { @@ -48,6 +58,22 @@ class CardMonthlyStatsInteractorFake: CardMonthlyStatsInteractorSpy { callback(result) } } + + var nextIsStatementsFeatureEnabledResult: Bool? + override func isStatementsFeatureEnabled(callback: @escaping (_ isEnabled: Bool) -> Void) { + super.isStatementsFeatureEnabled(callback: callback) + if let result = nextIsStatementsFeatureEnabledResult { + callback(result) + } + } + + var nextFetchStatementsPeriodResult: Result? + override func fetchStatementsPeriod(callback: @escaping Result.Callback) { + super.fetchStatementsPeriod(callback: callback) + if let result = nextFetchStatementsPeriodResult { + callback(result) + } + } } class CardMonthlyStatsPresenterSpy: CardMonthlyStatsPresenterProtocol { diff --git a/Example/UnitTests/shiftcardkit/Statements/MonthList/MonthlyStatementsListPresenterTest.swift b/Example/UnitTests/shiftcardkit/Statements/MonthList/MonthlyStatementsListPresenterTest.swift index c832dd2..6497edf 100644 --- a/Example/UnitTests/shiftcardkit/Statements/MonthList/MonthlyStatementsListPresenterTest.swift +++ b/Example/UnitTests/shiftcardkit/Statements/MonthList/MonthlyStatementsListPresenterTest.swift @@ -55,6 +55,7 @@ class MonthlyStatementsListPresenterTest: XCTestCase { // Then let months = sut.viewModel.months + XCTAssertTrue(sut.viewModel.dataLoaded.value) XCTAssertEqual(2, months.numberOfSections) XCTAssertEqual(1, months.numberOfItems(inSection: 0)) XCTAssertEqual(1, months.numberOfItems(inSection: 1)) @@ -87,6 +88,7 @@ class MonthlyStatementsListPresenterTest: XCTestCase { // Then XCTAssertEqual(error, sut.viewModel.error.value) + XCTAssertFalse(sut.viewModel.dataLoaded.value) } func testCloseTappedCallClose() { diff --git a/Example/UnitTests/shiftcardkit/WaitListModule/CardWaitListModuleTest.swift b/Example/UnitTests/shiftcardkit/WaitListModule/CardWaitListModuleTest.swift index 279777e..e858d35 100644 --- a/Example/UnitTests/shiftcardkit/WaitListModule/CardWaitListModuleTest.swift +++ b/Example/UnitTests/shiftcardkit/WaitListModule/CardWaitListModuleTest.swift @@ -17,27 +17,48 @@ class CardWaitListModuleTest: XCTestCase { private let card = ModelDataProvider.provider.waitListedCard private let cardProduct = ModelDataProvider.provider.cardProduct private lazy var presenter = serviceLocator.presenterLocatorFake.cardWaitListPresenterSpy + private lazy var platform = serviceLocator.platformFake override func setUp() { super.setUp() - sut = CardWaitListModule(serviceLocator: serviceLocator, card: card, cardProduct: cardProduct) + sut = CardWaitListModule(serviceLocator: serviceLocator, card: card) } - func testInitializeCallbackSuccess() { + func testInitializeCallFetchCardProduct() { + // When + sut.initialize { _ in } + + // Then + XCTAssertTrue(platform.fetchCardProductCalled) + } + + func testCardProductLoadFailsCallbackFailure() { // Given - var returnedResult: Result? + platform.nextFetchCardProductResult = .failure(BackendError(code: .other)) // When sut.initialize { result in - returnedResult = result + // Then + XCTAssertTrue(result.isFailure) } + } - // Then - XCTAssertEqual(true, returnedResult?.isSuccess) + func testCardProductLoadSucceedInitializeCallbackSuccess() { + // Given + platform.nextFetchCardProductResult = .success(cardProduct) + + // When + sut.initialize { result in + // Then + XCTAssertTrue(result.isSuccess) + } } func testInitializeSetUpPresenter() { + // Given + platform.nextFetchCardProductResult = .success(cardProduct) + // When sut.initialize { _ in } diff --git a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthContract.swift b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthContract.swift index 7f61ba4..000fbf9 100644 --- a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthContract.swift +++ b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthContract.swift @@ -51,4 +51,5 @@ class ExternalOAuthViewModel { let newUserAction: Observable = Observable(nil) let error: Observable = Observable(nil) let allowedBalanceTypes: Observable<[AllowedBalanceType]?> = Observable(nil) + let assetUrl: Observable = Observable(nil) } diff --git a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthModuleConfig.swift b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthModuleConfig.swift index 5d19466..a5f6806 100644 --- a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthModuleConfig.swift +++ b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthModuleConfig.swift @@ -13,5 +13,6 @@ struct ExternalOAuthModuleConfig { let callToAction: String let newUserAction: String let allowedBalanceTypes: [AllowedBalanceType] + let assetUrl: URL? let oauthErrorMessageKeys: [String]? } diff --git a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthPresenter.swift b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthPresenter.swift index 37dbac9..8343a46 100644 --- a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthPresenter.swift +++ b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/ExternalOAuthPresenter.swift @@ -30,6 +30,7 @@ class ExternalOAuthPresenter: ExternalOAuthPresenterProtocol { viewModel.callToAction.send(config.callToAction) viewModel.newUserAction.send(config.newUserAction) viewModel.allowedBalanceTypes.send(config.allowedBalanceTypes) + viewModel.assetUrl.send(config.assetUrl) analyticsManager?.track(event: Event.selectBalanceStoreLogin) } diff --git a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/View/ExternalOAuthViewControllerTheme2.swift b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/View/ExternalOAuthViewControllerTheme2.swift index 9be43e8..6bef2c7 100644 --- a/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/View/ExternalOAuthViewControllerTheme2.swift +++ b/Pod/Classes/ui/AccountsKit/ExternalOAuthModule/View/ExternalOAuthViewControllerTheme2.swift @@ -19,6 +19,7 @@ class ExternalOAuthViewControllerTheme2: ShiftViewController { private var actionButton: UIButton! private var newUserButton: ContentPresenterView! // swiftlint:enable implicitly_unwrapped_optional + private let imageView = UIImageView() private var allowedBalanceTypes = [AllowedBalanceType]() init(uiConfiguration: UIConfig, eventHandler: ExternalOAuthPresenterProtocol) { @@ -89,6 +90,15 @@ private extension ExternalOAuthViewControllerTheme2 { viewModel.error.ignoreNils().observeNext { [unowned self] error in self.show(error: error) }.dispose(in: disposeBag) + + viewModel.assetUrl.observeNext { [unowned self] url in + if let url = url { + self.imageView.setImageUrl(url) + } + else { + self.imageView.image = nil + } + }.dispose(in: disposeBag) } } @@ -102,6 +112,7 @@ private extension ExternalOAuthViewControllerTheme2 { setUpExplanationLabel() setUpNewUserButton() setUpActionButton() + setUpImageView() } func setUpNavigationBar() { @@ -147,6 +158,16 @@ private extension ExternalOAuthViewControllerTheme2 { make.bottom.equalTo(newUserButton.snp.top) } } + + func setUpImageView() { + imageView.contentMode = .center + view.addSubview(imageView) + imageView.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(24) + make.top.equalTo(explanationLabel.snp.bottom).offset(24) + make.bottom.equalTo(actionButton.snp.top).offset(-24) + } + } } extension ExternalOAuthViewControllerTheme2: ContentPresenterViewDelegate { diff --git a/Pod/Classes/ui/AccountsKit/SelectBalanceStoreModule/SelectBalanceStoreModule.swift b/Pod/Classes/ui/AccountsKit/SelectBalanceStoreModule/SelectBalanceStoreModule.swift index 6d590e0..b784d8c 100644 --- a/Pod/Classes/ui/AccountsKit/SelectBalanceStoreModule/SelectBalanceStoreModule.swift +++ b/Pod/Classes/ui/AccountsKit/SelectBalanceStoreModule/SelectBalanceStoreModule.swift @@ -33,6 +33,7 @@ class SelectBalanceStoreModule: UIModule, SelectBalanceStoreModuleProtocol { externalOAuthModuleConfig = ExternalOAuthModuleConfig(title: title, explanation: explanation, callToAction: callToAction, newUserAction: newUserAction, allowedBalanceTypes: action.allowedBalanceTypes, + assetUrl: action.assetUrl, oauthErrorMessageKeys: nil) super.init(serviceLocator: serviceLocator) } diff --git a/Pod/Classes/ui/AptoPlatformUI.swift b/Pod/Classes/ui/AptoPlatformUI.swift index 70cbe3e..b9a51df 100644 --- a/Pod/Classes/ui/AptoPlatformUI.swift +++ b/Pod/Classes/ui/AptoPlatformUI.swift @@ -10,13 +10,13 @@ import AptoSDK // Extensions do not allow properties, instead of getting crazy with associated objects we create private propeties. // This properties are made lazy by the compiler. -private var _initialModule: UIModuleProtocol? = nil -private var _uiDelegate: AptoPlatformUIDelegate? = nil +private var _initialModule: UIModuleProtocol? +private weak var _uiDelegate: AptoPlatformUIDelegate? private let lockingQueue = DispatchQueue(label: "com.aptotpayments.ios.sdk.locking") private var isPresentingNetworkNotReachable = false private var fontRegistered = false private let queue = DispatchQueue(label: "com.aptopayments.sdk.register.fonts") -private var _googleMapsApiKey: String? = nil +private var _googleMapsApiKey: String? extension AptoPlatformProtocol { var googleMapsApiKey: String? { @@ -34,7 +34,7 @@ extension AptoPlatform { _initialModule = newValue } } - public var uiDelegate: AptoPlatformUIDelegate? { + public weak var uiDelegate: AptoPlatformUIDelegate? { get { return _uiDelegate } @@ -111,7 +111,7 @@ extension AptoPlatform { // MARK: Network connection error @objc private func didRestoreNetworkConnectionUI() { - if uiDelegate?.shouldShowNoNetworkUI?() == false { return } + if uiDelegate?.shouldShowNoNetworkUI?() == false || !isPresentingNetworkNotReachable { return } dismissNetworkNotReachableError() } diff --git a/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsContract.swift b/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsContract.swift index 2e0a02e..7d1bbde 100644 --- a/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsContract.swift +++ b/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsContract.swift @@ -15,6 +15,8 @@ protocol CardMonthlyStatsModuleProtocol: UIModuleProtocol { protocol CardMonthlyStatsInteractorProtocol { func fetchMonthlySpending(date: Date, callback: @escaping Result.Callback) + func isStatementsFeatureEnabled(callback: @escaping (_ isEnabled: Bool) -> Void) + func fetchStatementsPeriod(callback: @escaping Result.Callback) } class CardMonthlyStatsViewModel { @@ -22,7 +24,7 @@ class CardMonthlyStatsViewModel { let previousSpendingExists: Observable = Observable(true) let nextSpendingExists: Observable = Observable(true) let dataLoaded: Observable = Observable(false) - let monthlyStatementsAvailable: Observable = Observable(true) + let monthlyStatementsAvailable: Observable = Observable(false) } protocol CardMonthlyStatsPresenterProtocol: class { diff --git a/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsInteractor.swift b/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsInteractor.swift index 31eeb16..1f239da 100644 --- a/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsInteractor.swift +++ b/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsInteractor.swift @@ -28,4 +28,12 @@ class CardMonthlyStatsInteractor: CardMonthlyStatsInteractorProtocol { } } } + + func isStatementsFeatureEnabled(callback: @escaping (Bool) -> Void) { + callback(platform.isFeatureEnabled(.showMonthlyStatementsOption)) + } + + func fetchStatementsPeriod(callback: @escaping Result.Callback) { + platform.fetchMonthlyStatementsPeriod(callback: callback) + } } diff --git a/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsPresenter.swift b/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsPresenter.swift index 7b994be..cf33485 100644 --- a/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsPresenter.swift +++ b/Pod/Classes/ui/CardKit/CardMonthlyStatsModule/CardMonthlyStatsPresenter.swift @@ -15,10 +15,17 @@ class CardMonthlyStatsPresenter: CardMonthlyStatsPresenterProtocol { let viewModel = CardMonthlyStatsViewModel() var analyticsManager: AnalyticsServiceProtocol? private var spendingByMonth = [String: MonthlySpending]() + private var monthlyStatementsPeriod: MonthlyStatementsPeriod? + private var currentDate: Date? { + didSet { + updateMonthlyStatementsAvailable() + } + } func viewLoaded() { loadSpending(for: Date()) analyticsManager?.track(event: Event.monthlySpending) + loadStatementsPeriod() } func closeTapped() { @@ -39,7 +46,24 @@ class CardMonthlyStatsPresenter: CardMonthlyStatsPresenterProtocol { } // MARK: - Private methods + private func loadStatementsPeriod() { + interactor?.isStatementsFeatureEnabled(callback: { [weak self] isEnabled in + if isEnabled { + self?.interactor?.fetchStatementsPeriod(callback: { [weak self] result in + self?.monthlyStatementsPeriod = result.value + self?.updateMonthlyStatementsAvailable() + }) + } + }) + } + + private func updateMonthlyStatementsAvailable() { + guard let period = self.monthlyStatementsPeriod, let currentDate = self.currentDate else { return } + viewModel.monthlyStatementsAvailable.send(period.includes(month: Month(from: currentDate))) + } + private func loadSpending(for date: Date) { + self.currentDate = date if let spending = cachedSpending(for: date) { updateViewModelWith(spending: spending) return diff --git a/Pod/Classes/ui/CardKit/ManageShiftCardModule/FundingSourceSelector/FundingSourceSelectorModule.swift b/Pod/Classes/ui/CardKit/ManageShiftCardModule/FundingSourceSelector/FundingSourceSelectorModule.swift index a65c2d1..dfbfd0c 100644 --- a/Pod/Classes/ui/CardKit/ManageShiftCardModule/FundingSourceSelector/FundingSourceSelectorModule.swift +++ b/Pod/Classes/ui/CardKit/ManageShiftCardModule/FundingSourceSelector/FundingSourceSelectorModule.swift @@ -36,7 +36,7 @@ class FundingSourceSelectorModule: UIModule, FundingSourceSelectorModuleProtocol let newUserAction = "external_auth.login.new_user.title".podLocalized() let oauthModuleConfig = ExternalOAuthModuleConfig(title: title, explanation: explanation, callToAction: callToAction, newUserAction: newUserAction, - allowedBalanceTypes: allowedBalanceTypes, + allowedBalanceTypes: allowedBalanceTypes, assetUrl: nil, oauthErrorMessageKeys: oauthErrorMessageKeys) let externalOAuthModule = ExternalOAuthModule(serviceLocator: serviceLocator, config: oauthModuleConfig, diff --git a/Pod/Classes/ui/CardKit/ManageShiftCardModule/ManageShiftCardModule.swift b/Pod/Classes/ui/CardKit/ManageShiftCardModule/ManageShiftCardModule.swift index c6ccdf9..b706a16 100644 --- a/Pod/Classes/ui/CardKit/ManageShiftCardModule/ManageShiftCardModule.swift +++ b/Pod/Classes/ui/CardKit/ManageShiftCardModule/ManageShiftCardModule.swift @@ -16,7 +16,6 @@ class ManageShiftCardModule: UIModule { private var accountSettingsModule: UIModuleProtocol? private var projectConfiguration: ProjectConfiguration! // swiftlint:disable:this implicitly_unwrapped_optional private var mailSender: MailSender? - private var cardProduct: CardProduct? private var presenter: ManageShiftCardPresenterProtocol? private var kycPresenter: KYCPresenterProtocol? private var transactionDetailsPresenter: ShiftCardTransactionDetailsPresenterProtocol? @@ -53,29 +52,13 @@ class ManageShiftCardModule: UIModule { case .failure(let error): completion(.failure(error)) case .success(let financialAccount): - guard let card = financialAccount as? Card, let cardProductId = card.cardProductId else { - return - } + guard let card = financialAccount as? Card else { return } self.card = card - self.platform.fetchCardProduct(cardProductId: cardProductId) { [weak self] result in - guard let self = self else { return } - switch result { - case .failure(let error): - completion(.failure(error)) - case .success(let cardProduct): - self.cardProduct = cardProduct - if let kyc = self.card.kyc { - switch kyc { - case .passed: - self.showManageCard(addChild: true, cardProduct: cardProduct, completion: completion) - default: - self.showKYCViewController(addChild: true, card: self.card, completion: completion) - } - } - else { - self.showManageCard(addChild: true, cardProduct: cardProduct, completion: completion) - } - } + if let kyc = self.card.kyc, kyc != .passed { + self.showKYCViewController(addChild: true, card: self.card, completion: completion) + } + else { + self.showManageCard(addChild: true, completion: completion) } } } @@ -100,10 +83,9 @@ class ManageShiftCardModule: UIModule { // MARK: - Manage Card View Controller - private func showManageCard(addChild: Bool, cardProduct: CardProduct, - completion: @escaping Result.Callback) { + private func showManageCard(addChild: Bool, completion: @escaping Result.Callback) { if card.isInWaitList == true { - showWaitListModule(addChild: addChild, cardProduct: cardProduct, completion: completion) + showWaitListModule(addChild: addChild, completion: completion) } else if card.state == .created && card.orderedStatus == .ordered { showPhysicalCardActivationModule(addChild: addChild, completion: completion) @@ -111,11 +93,12 @@ class ManageShiftCardModule: UIModule { else { showManageCardViewController(addChild: addChild, completion: completion) } + // This is an optimization to have the card product preloaded when the user taps on card settings + fetchCardProduct(card: card) } - private func showWaitListModule(addChild: Bool, cardProduct: CardProduct, - completion: @escaping Result.Callback) { - let module = serviceLocator.moduleLocator.cardWaitListModule(card: card, cardProduct: cardProduct) + private func showWaitListModule(addChild: Bool, completion: @escaping Result.Callback) { + let module = serviceLocator.moduleLocator.cardWaitListModule(card: card) self.cardWaitListModule = module if addChild { module.onFinish = { [weak self] _ in @@ -236,6 +219,12 @@ class ManageShiftCardModule: UIModule { self.kycPresenter = presenter return viewController } + + // MARK: - Fetch card product + private func fetchCardProduct(card: Card) { + guard let cardProductId = card.cardProductId else { return } + platform.fetchCardProduct(cardProductId: cardProductId, forceRefresh: false) { _ in } + } } extension ManageShiftCardModule: ManageShiftCardRouterProtocol { @@ -256,9 +245,9 @@ extension ManageShiftCardModule: ManageShiftCardRouterProtocol { self.close() case .success(let cards): let nonClosedCards = cards.filter { $0.state != .cancelled } - if let card = nonClosedCards.first, let cardProduct = self.cardProduct { + if let card = nonClosedCards.first { self.card = card - self.showManageCard(addChild: false, cardProduct: cardProduct) { _ in } + self.showManageCard(addChild: false) { _ in } } else { // Close the SDK @@ -392,27 +381,23 @@ extension ManageShiftCardModule: KYCRouterProtocol { } func kycPassed() { - if let cardProduct = self.cardProduct { - platform.fetchFinancialAccount(self.card.accountId, forceRefresh: true, - retrieveBalances: false) { [weak self] result in - guard let self = self else { return } - switch result { - case .failure(let error): - self.show(error: error) - case .success(let financialAccount): - guard let shiftCard = financialAccount as? Card else { - return - } - self.card = shiftCard - if self.isPresentingModalKYC { - self.isPresentingModalKYC = false - UIApplication.topViewController()?.dismiss(animated: true) - self.platform.runPendingNetworkRequests() - } - else { - self.popViewController(animated: false) { - self.showManageCard(addChild: false, cardProduct: cardProduct) { _ in } - } + platform.fetchFinancialAccount(self.card.accountId, forceRefresh: true, + retrieveBalances: false) { [weak self] result in + guard let self = self else { return } + switch result { + case .failure(let error): + self.show(error: error) + case .success(let financialAccount): + guard let shiftCard = financialAccount as? Card else { return } + self.card = shiftCard + if self.isPresentingModalKYC { + self.isPresentingModalKYC = false + UIApplication.topViewController()?.dismiss(animated: true) + self.platform.runPendingNetworkRequests() + } + else { + self.popViewController(animated: false) { + self.showManageCard(addChild: false) { _ in } } } } diff --git a/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsPresenter.swift b/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsPresenter.swift index 7237085..89ad1fa 100644 --- a/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsPresenter.swift +++ b/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsPresenter.swift @@ -60,7 +60,12 @@ class ShiftCardTransactionDetailsPresenter: ShiftCardTransactionDetailsPresenter viewModel.mccIcon.send(transaction.merchant?.mcc?.icon) viewModel.description.send(transaction.transactionDescription?.capitalized) viewModel.fiatAmount.send(transaction.localAmountRepresentation) - viewModel.nativeAmount.send(transaction.nativeBalance?.absText) + if transaction.localAmount?.currency.value != transaction.nativeBalance?.currency.value { + viewModel.nativeAmount.send(transaction.nativeBalance?.absText) + } + else { + viewModel.nativeAmount.send(nil) + } //address viewModel.transactionDate.send(self.format(date: transaction.createdAt)) viewModel.transactionStatus.send(transaction.state.description()) diff --git a/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsViewControllerTheme2.swift b/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsViewControllerTheme2.swift index c0eec22..414665c 100644 --- a/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsViewControllerTheme2.swift +++ b/Pod/Classes/ui/CardKit/ManageShiftCardModule/TransactionDetails/ShiftCardTransactionDetailsViewControllerTheme2.swift @@ -230,7 +230,9 @@ class ShiftCardTransactionDetailsViewControllerTheme2: ShiftViewController, Shif if uiConfiguration.transactionDetailsShowDetailsSectionTitle { addDetailsTitleRow(to: &rows) } - showDetails = !uiConfiguration.transactionDetailsShowDetailsSectionTitle + else { + showDetails = true + } if showDetails { addRowIfHasValue(rows: &rows, title: "transaction_details.basic_info.transaction_status.title".podLocalized(), @@ -260,6 +262,8 @@ class ShiftCardTransactionDetailsViewControllerTheme2: ShiftViewController, Shif default: title = "" } + let accountName = viewModel.fundingSourceName.value ?? "" + title = title.replace(["%@": accountName]) var exchangeRate: String = "" if let adjustmentExchangeRate = adjustment.exchangeRate, let localCurrency = adjustment.localAmount?.currencySymbol, diff --git a/Pod/Classes/ui/CardKit/Statements/MonthList/Cell/MonthlyStatementListCell.swift b/Pod/Classes/ui/CardKit/Statements/MonthList/Cell/MonthlyStatementListCell.swift index 1677e4d..1c5cb10 100644 --- a/Pod/Classes/ui/CardKit/Statements/MonthList/Cell/MonthlyStatementListCell.swift +++ b/Pod/Classes/ui/CardKit/Statements/MonthList/Cell/MonthlyStatementListCell.swift @@ -38,10 +38,13 @@ class MonthlyStatementListCell: UITableViewCell { func setUIConfig(_ uiConfiguration: UIConfig) { guard !self.styleInitialized else { return } uiConfig = uiConfiguration - contentView.backgroundColor = uiConfiguration.uiBackgroundSecondaryColor + backgroundColor = uiConfiguration.uiBackgroundSecondaryColor + contentView.backgroundColor = backgroundColor + backgroundView?.backgroundColor = backgroundColor label.font = uiConfiguration.fontProvider.mainItemRegularFont label.textColor = uiConfiguration.textPrimaryColor bottomDividerView.backgroundColor = uiConfiguration.uiTertiaryColor + accessoryView?.tintColor = uiConfiguration.uiTertiaryColor styleInitialized = true } @@ -58,6 +61,7 @@ private extension MonthlyStatementListCell { func setUpUI() { setUpLabel() setUpBottomDivider() + setUpAccessoryView() } func setUpLabel() { @@ -77,4 +81,8 @@ private extension MonthlyStatementListCell { make.bottom.equalToSuperview() } } + + func setUpAccessoryView() { + accessoryView = UIImageView(image: UIImage.imageFromPodBundle("row_arrow")?.asTemplate()) + } } diff --git a/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListContract.swift b/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListContract.swift index abdbc25..9354eb5 100644 --- a/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListContract.swift +++ b/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListContract.swift @@ -18,6 +18,7 @@ protocol MonthlyStatementsListInteractorProtocol { class MonthlyStatementsListViewModel { let months: MutableObservable2DArray = MutableObservable2DArray([]) + let dataLoaded: Observable = Observable(false) let error: Observable = Observable(nil) } diff --git a/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListPresenter.swift b/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListPresenter.swift index fe1b8ac..4ea4903 100644 --- a/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListPresenter.swift +++ b/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListPresenter.swift @@ -30,6 +30,7 @@ class MonthlyStatementsListPresenter: MonthlyStatementsListPresenterProtocol { // MARK: Private methods private func loadStatementsPeriod() { + viewModel.dataLoaded.send(false) router?.showLoadingView() interactor?.fetchStatementsPeriod { [weak self] result in self?.router?.hideLoadingView() @@ -38,6 +39,7 @@ class MonthlyStatementsListPresenter: MonthlyStatementsListPresenterProtocol { self?.viewModel.error.send(error) case .success(let statementsPeriod): self?.updatePeriod(statementsPeriod) + self?.viewModel.dataLoaded.send(true) } } } diff --git a/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListViewController.swift b/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListViewController.swift index e374c83..55a9a14 100644 --- a/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListViewController.swift +++ b/Pod/Classes/ui/CardKit/Statements/MonthList/MonthlyStatementsListViewController.swift @@ -15,6 +15,7 @@ class MonthlyStatementsListViewController: ShiftViewController { private let disposeBag = DisposeBag() private unowned let presenter: MonthlyStatementsListPresenterProtocol private let tableView = UITableView(frame: .zero, style: .grouped) + private let emptyCaseView = UIView() static private let _dateFormatter = DateFormatter.customLocalizedDateFormatter(dateFormat: "MMMM") private lazy var dateFormatter = MonthlyStatementsListViewController._dateFormatter @@ -98,6 +99,10 @@ private extension MonthlyStatementsListViewController { viewModel.months.observeNext { [weak self] _ in self?.tableView.reloadData() }.dispose(in: disposeBag) + combineLatest(viewModel.dataLoaded, viewModel.months).observeNext { [weak self] dataLoaded, months in + guard dataLoaded else { return } + self?.emptyCaseView.isHidden = !months.source.isEmpty + }.dispose(in: disposeBag) } } @@ -108,6 +113,7 @@ private extension MonthlyStatementsListViewController { setUpTitle() setUpNavigationBar() setUpTableView() + setUpEmptyCase() } func setUpTitle() { @@ -116,7 +122,8 @@ private extension MonthlyStatementsListViewController { func setUpNavigationBar() { navigationController?.navigationBar.setUp(barTintColor: uiConfiguration.uiNavigationSecondaryColor, - tintColor: uiConfiguration.iconTertiaryColor) + tintColor: uiConfiguration.textTopBarSecondaryColor) + navigationItem.leftBarButtonItem?.tintColor = uiConfiguration.textTopBarSecondaryColor setNeedsStatusBarAppearanceUpdate() } @@ -124,7 +131,8 @@ private extension MonthlyStatementsListViewController { tableView.backgroundColor = view.backgroundColor view.addSubview(tableView) tableView.snp.makeConstraints { make in - make.edges.equalToSuperview() + make.top.equalToSuperview().offset(16) + make.left.bottom.right.equalToSuperview() } tableView.separatorStyle = .none tableView.rowHeight = 74 @@ -132,4 +140,23 @@ private extension MonthlyStatementsListViewController { tableView.dataSource = self tableView.delegate = self } + + func setUpEmptyCase() { + emptyCaseView.isHidden = true + view.addSubview(emptyCaseView) + emptyCaseView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + let message = "monthly_statements.list.empty_case.message".podLocalized() + let label = ComponentCatalog.sectionTitleLabelWith(text: message, + textAlignment: .center, + uiConfig: uiConfiguration) + label.textColor = uiConfiguration.textTertiaryColor + label.numberOfLines = 0 + emptyCaseView.addSubview(label) + label.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(16) + make.centerY.equalToSuperview() + } + } } diff --git a/Pod/Classes/ui/CardKit/Statements/Report/MonthlyStatementsReportViewController.swift b/Pod/Classes/ui/CardKit/Statements/Report/MonthlyStatementsReportViewController.swift index 1a6df0c..0b9f57c 100644 --- a/Pod/Classes/ui/CardKit/Statements/Report/MonthlyStatementsReportViewController.swift +++ b/Pod/Classes/ui/CardKit/Statements/Report/MonthlyStatementsReportViewController.swift @@ -93,6 +93,7 @@ private extension MonthlyStatementsReportViewController { func setUpNavigationBar() { navigationController?.navigationBar.setUp(barTintColor: uiConfiguration.uiNavigationSecondaryColor, tintColor: uiConfiguration.textTopBarSecondaryColor) + navigationItem.leftBarButtonItem?.tintColor = uiConfiguration.textTopBarSecondaryColor } func setUpShareButton() { diff --git a/Pod/Classes/ui/CardKit/WaitListModule/CardWaitListModule.swift b/Pod/Classes/ui/CardKit/WaitListModule/CardWaitListModule.swift index 24e05f7..e44302d 100644 --- a/Pod/Classes/ui/CardKit/WaitListModule/CardWaitListModule.swift +++ b/Pod/Classes/ui/CardKit/WaitListModule/CardWaitListModule.swift @@ -9,25 +9,35 @@ import AptoSDK class CardWaitListModule: UIModule, CardWaitListModuleProtocol { private let card: Card - private let cardProduct: CardProduct private var presenter: CardWaitListPresenterProtocol? - init(serviceLocator: ServiceLocatorProtocol, card: Card, cardProduct: CardProduct) { + init(serviceLocator: ServiceLocatorProtocol, card: Card) { self.card = card - self.cardProduct = cardProduct super.init(serviceLocator: serviceLocator) } override func initialize(completion: @escaping Result.Callback) { - let viewController = buildViewController() - completion(.success(viewController)) + guard let cardProductId = card.cardProductId else { + completion(.failure(ServiceError(code: .internalIncosistencyError))) + return + } + platform.fetchCardProduct(cardProductId: cardProductId, forceRefresh: false) { [weak self] result in + switch result { + case .failure(let error): + completion(.failure(error)) + case .success(let cardProduct): + guard let self = self else { return } + let viewController = self.buildViewController(cardProduct: cardProduct) + completion(.success(viewController)) + } + } } func cardStatusChanged() { onFinish?(self) } - private func buildViewController() -> UIViewController { + private func buildViewController(cardProduct: CardProduct) -> UIViewController { let interactor = serviceLocator.interactorLocator.cardWaitListInteractor(card: card) let config = WaitListActionConfiguration(asset: cardProduct.waitListAsset, backgroundImage: cardProduct.waitListBackgroundImage, diff --git a/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocator.swift b/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocator.swift index c39888b..21c6be1 100644 --- a/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocator.swift +++ b/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocator.swift @@ -84,8 +84,8 @@ final class ModuleLocator: ModuleLocatorProtocol { return WaitListModule(serviceLocator: serviceLocator, cardApplication: application) } - func cardWaitListModule(card: Card, cardProduct: CardProduct) -> CardWaitListModuleProtocol { - return CardWaitListModule(serviceLocator: serviceLocator, card: card, cardProduct: cardProduct) + func cardWaitListModule(card: Card) -> CardWaitListModuleProtocol { + return CardWaitListModule(serviceLocator: serviceLocator, card: card) } // MARK: - Errors diff --git a/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocatorProtocol.swift b/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocatorProtocol.swift index 3086cc6..8204928 100644 --- a/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocatorProtocol.swift +++ b/Pod/Classes/ui/ServiceLocator/ModuleLocator/ModuleLocatorProtocol.swift @@ -32,7 +32,7 @@ protocol ModuleLocatorProtocol { func verifyDocumentModule(workflowObject: WorkflowObject?) -> VerifyDocumentModule func issueCardModule(application: CardApplication) -> UIModuleProtocol func waitListModule(application: CardApplication) -> WaitListModuleProtocol - func cardWaitListModule(card: Card, cardProduct: CardProduct) -> CardWaitListModuleProtocol + func cardWaitListModule(card: Card) -> CardWaitListModuleProtocol // MARK: - Errors func serverMaintenanceErrorModule() -> ServerMaintenanceErrorModuleProtocol diff --git a/Pod/Classes/ui/UIKit/Components/ContentPresenterView.swift b/Pod/Classes/ui/UIKit/Components/ContentPresenterView.swift index da444a4..4f5fa50 100644 --- a/Pod/Classes/ui/UIKit/Components/ContentPresenterView.swift +++ b/Pod/Classes/ui/UIKit/Components/ContentPresenterView.swift @@ -209,9 +209,7 @@ extension ContentPresenterView: TTTAttributedLabelDelegate { extension ContentPresenterView: WKNavigationDelegate { func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - guard (error as NSError).code != -999 else { - return - } + guard (error as NSError).code != NSURLErrorCancelled else { return } delegate?.hideLoadingSpinner() delegate?.show(error: error) } diff --git a/Pod/Classes/ui/UIKit/Components/UIPinEntryTextField.swift b/Pod/Classes/ui/UIKit/Components/UIPinEntryTextField.swift index 777cefa..0dee916 100644 --- a/Pod/Classes/ui/UIKit/Components/UIPinEntryTextField.swift +++ b/Pod/Classes/ui/UIKit/Components/UIPinEntryTextField.swift @@ -216,6 +216,7 @@ class UIPinEntryTextField: UIView { /// Reset text values func resetText() { + currentTextField = nil textFields.forEach { $0.text = "" } } diff --git a/Pod/Classes/ui/UIKit/Components/UIView.swift b/Pod/Classes/ui/UIKit/Components/UIView.swift index c2dd6b6..d7a5c56 100644 --- a/Pod/Classes/ui/UIKit/Components/UIView.swift +++ b/Pod/Classes/ui/UIKit/Components/UIView.swift @@ -64,17 +64,26 @@ extension UIView { isError: Bool, uiConfig: UIConfig, tapHandler: (() -> Void)?) { - UIApplication.topViewController()?.show(message: message, - title: title, - animated: animated, - isError: isError, - uiConfig: uiConfig, - tapHandler: tapHandler) + let topViewController = UIApplication.topViewController() + guard parentViewController === topViewController else { return } + topViewController?.show(message: message, title: title, animated: animated, isError: isError, uiConfig: uiConfig, + tapHandler: tapHandler) } func hideMessage(animated: Bool = true) { UIApplication.topViewController()?.dismissToast(animated) } + + private var parentViewController: UIViewController? { + var parentResponder: UIResponder? = self + while parentResponder != nil { + parentResponder = parentResponder?.next + if let viewController = parentResponder as? UIViewController { + return viewController + } + } + return nil + } } // Gesture Recognizers diff --git a/Pod/Classes/ui/UIKit/Components/UIWebView.swift b/Pod/Classes/ui/UIKit/Components/UIWebView.swift deleted file mode 100644 index 9678cd8..0000000 --- a/Pod/Classes/ui/UIKit/Components/UIWebView.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// UIWebView.swift -// AptoSDK -// -// Created by Ivan Oliver Martínez on 23/10/2017. -// -// - -import UIKit - -extension UIWebView { - - static let markdownStyle = "body { font-family: '-apple-system','HelveticaNeue'; font-size:14; } h1 { font-size:18; } h2 { font-size:16; }" - -} diff --git a/Pod/Classes/ui/UIKit/ShowGenericMessageModule/ShowGenericMessageViewController.swift b/Pod/Classes/ui/UIKit/ShowGenericMessageModule/ShowGenericMessageViewController.swift index aa15f3d..3f9e644 100644 --- a/Pod/Classes/ui/UIKit/ShowGenericMessageModule/ShowGenericMessageViewController.swift +++ b/Pod/Classes/ui/UIKit/ShowGenericMessageModule/ShowGenericMessageViewController.swift @@ -9,6 +9,7 @@ import UIKit import AptoSDK import TTTAttributedLabel +import WebKit protocol ShowGenericMessageEventHandler { func viewLoaded() @@ -22,7 +23,7 @@ class ShowGenericMessageViewController: ShiftViewController, ShowGenericMessageV var eventHandler: ShowGenericMessageEventHandler fileprivate var logoImageView: UIImageView! - fileprivate let webView = UIWebView() + fileprivate let webView = WKWebView() fileprivate var callToActionButton: UIButton! init(uiConfiguration: UIConfig, eventHandler: ShowGenericMessageEventHandler) { @@ -74,9 +75,23 @@ class ShowGenericMessageViewController: ShiftViewController, ShowGenericMessageV if let text = content?.htmlString(font: self.uiConfiguration.fontProvider.timestampFont, color: self.uiConfiguration.noteTextColor, linkColor: self.uiConfiguration.tintColor) { - //let cssStyles = UIWebView.markdownStyle - //"body { font-family: '-apple-system','HelveticaNeue'; font-size:14; } h1 { font-size:18; } h2 { font-size:16; }" - let htmlDoc = "\(text)" + let markdownStyle = +""" +body { font-family: '-apple-system','HelveticaNeue'; font-size:14; } +h1 { font-size:18; } +h2 { font-size:16; } +""" + let htmlDoc = +""" + + + + + + \(text) + + +""" webView.loadHTMLString(htmlDoc, baseURL: nil) webView.isHidden = false } diff --git a/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme1.swift b/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme1.swift index 2a3c585..e43b4f2 100644 --- a/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme1.swift +++ b/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme1.swift @@ -8,9 +8,10 @@ import UIKit import AptoSDK +import WebKit class WebBrowserViewControllerTheme1: WebBrowserViewControllerProtocol { - private let webView = UIWebView() + private let webView = WKWebView() private unowned let eventHandler: WebBrowserEventHandlerProtocol private let alternativeTitle: String? @@ -36,7 +37,7 @@ class WebBrowserViewControllerTheme1: WebBrowserViewControllerProtocol { webView.snp.makeConstraints { make in make.top.left.right.bottom.equalTo(self.view) } - webView.delegate = self + webView.navigationDelegate = self } // MARK: WebBrowserViewProtocol @@ -47,7 +48,7 @@ class WebBrowserViewControllerTheme1: WebBrowserViewControllerProtocol { if let headers = headers { request.allHTTPHeaderFields = headers } - webView.loadRequest(request as URLRequest) + webView.load(request as URLRequest) } // MARK: Private @@ -57,32 +58,30 @@ class WebBrowserViewControllerTheme1: WebBrowserViewControllerProtocol { } } -extension WebBrowserViewControllerTheme1: UIWebViewDelegate { - func webViewDidFinishLoad(_ aWebView: UIWebView) { +extension WebBrowserViewControllerTheme1: WKNavigationDelegate { + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.hideLoadingSpinner() - guard let title = aWebView.stringByEvaluatingJavaScript(from: "document.title"), !title.isEmpty else { + guard let title = webView.title, !title.isEmpty else { self.title = alternativeTitle return } self.title = title } - func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { - guard (error as NSError).code != -999 else { - return - } - self.hideLoadingSpinner() - self.show(error: error) + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + guard (error as NSError).code != NSURLErrorCancelled else { return } + hideLoadingSpinner() + show(error: error) } - func webView(_ webView: UIWebView, - shouldStartLoadWith request: URLRequest, - navigationType: UIWebView.NavigationType) -> Bool { - if let urlString = request.url?.absoluteString, urlString.hasPrefix("shift-sdk://oauth-finish") { - defer { closeTapped() } - return false + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let urlString = navigationAction.request.url?.absoluteString, urlString.hasPrefix("shift-sdk://oauth-finish") { + decisionHandler(.cancel) + closeTapped() + } + else { + decisionHandler(.allow) } - - return true } } diff --git a/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme2.swift b/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme2.swift index d8a1064..1fc5b16 100644 --- a/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme2.swift +++ b/Pod/Classes/ui/UIKit/WebBrowser/View/WebBrowserViewControllerTheme2.swift @@ -8,9 +8,10 @@ import UIKit import AptoSDK import SnapKit +import WebKit class WebBrowserViewControllerTheme2: WebBrowserViewControllerProtocol { - private let webView = UIWebView() + private let webView = WKWebView() private unowned let presenter: WebBrowserEventHandlerProtocol private let alternativeTitle: String? @@ -36,7 +37,7 @@ class WebBrowserViewControllerTheme2: WebBrowserViewControllerProtocol { if let headers = headers { request.allHTTPHeaderFields = headers } - webView.loadRequest(request as URLRequest) + webView.load(request as URLRequest) } override func closeTapped() { @@ -44,33 +45,31 @@ class WebBrowserViewControllerTheme2: WebBrowserViewControllerProtocol { } } -extension WebBrowserViewControllerTheme2: UIWebViewDelegate { - func webViewDidFinishLoad(_ aWebView: UIWebView) { +extension WebBrowserViewControllerTheme2: WKNavigationDelegate { + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.hideLoadingSpinner() - guard let title = aWebView.stringByEvaluatingJavaScript(from: "document.title"), !title.isEmpty else { + guard let title = webView.title, !title.isEmpty else { self.title = alternativeTitle return } self.title = title } - func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { - guard (error as NSError).code != -999 else { - return - } - self.hideLoadingSpinner() - self.show(error: error) + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + guard (error as NSError).code != NSURLErrorCancelled else { return } + hideLoadingSpinner() + show(error: error) } - func webView(_ webView: UIWebView, - shouldStartLoadWith request: URLRequest, - navigationType: UIWebView.NavigationType) -> Bool { - if let urlString = request.url?.absoluteString, urlString.hasPrefix("shift-sdk://oauth-finish") { - defer { closeTapped() } - return false + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if let urlString = navigationAction.request.url?.absoluteString, urlString.hasPrefix("shift-sdk://oauth-finish") { + decisionHandler(.cancel) + closeTapped() + } + else { + decisionHandler(.allow) } - - return true } } @@ -91,6 +90,6 @@ private extension WebBrowserViewControllerTheme2 { webView.snp.makeConstraints { make in make.edges.equalToSuperview() } - webView.delegate = self + webView.navigationDelegate = self } } diff --git a/Pod/Localization/en.lproj/Localizable.strings b/Pod/Localization/en.lproj/Localizable.strings index 0c3f8e4..510a4f0 100644 --- a/Pod/Localization/en.lproj/Localizable.strings +++ b/Pod/Localization/en.lproj/Localizable.strings @@ -1642,6 +1642,8 @@ "monthly_statements.list.error_generating_report.message" = "There was an error generating the report. Please try again later or contact customer support if the error persist"; +"monthly_statements.list.empty_case.message" = "No statements available yet."; + "monthly_statements.report.title" = "Statement <> <>"; "monthly_statements.report.error_url_expired.message" = "For security reasons this file has expired, please request it again";