Skip to content
Draft
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
20 changes: 20 additions & 0 deletions novawallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,9 @@
2D9837A62F310CE000B184D4 /* WOScamAlertSheetPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D98379A2F310CE000B184D4 /* WOScamAlertSheetPresenter.swift */; };
2D9837BC2F3128C000B184D4 /* WOScamAlertSheetViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9837BB2F3128C000B184D4 /* WOScamAlertSheetViewModelFactory.swift */; };
2D9837EB2F313F5900B184D4 /* CreateWatchOnlyViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9837EA2F313F5900B184D4 /* CreateWatchOnlyViewController+Keyboard.swift */; };
2D9836B02F2938D600B184D4 /* ASMRemoteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9836AF2F2938D600B184D4 /* ASMRemoteData.swift */; };
2D9836BB2F29391600B184D4 /* ASMInfoFetchOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9836BA2F29391600B184D4 /* ASMInfoFetchOperationFactory.swift */; };
2D9836C02F2939D100B184D4 /* ASMInfoRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9836BF2F2939D100B184D4 /* ASMInfoRepository.swift */; };
2DA3AE8F2E5DB03300689F65 /* XcmDynamicModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3D1B1D2DA3BE0A0008057B /* XcmDynamicModelFactory.swift */; };
2DA3AE902E5DB04200689F65 /* XcmUni.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED71C42D9E957400F79C02 /* XcmUni.swift */; };
2DA3AE912E5DB06200689F65 /* XcmUni+Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED71C62D9E95EC00F79C02 /* XcmUni+Location.swift */; };
Expand Down Expand Up @@ -8716,6 +8719,9 @@
2D98379F2F310CE000B184D4 /* WOScamAlertSheetWireframe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WOScamAlertSheetWireframe.swift; sourceTree = "<group>"; };
2D9837BB2F3128C000B184D4 /* WOScamAlertSheetViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WOScamAlertSheetViewModelFactory.swift; sourceTree = "<group>"; };
2D9837EA2F313F5900B184D4 /* CreateWatchOnlyViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreateWatchOnlyViewController+Keyboard.swift"; sourceTree = "<group>"; };
2D9836AF2F2938D600B184D4 /* ASMRemoteData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMRemoteData.swift; sourceTree = "<group>"; };
2D9836BA2F29391600B184D4 /* ASMInfoFetchOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMInfoFetchOperationFactory.swift; sourceTree = "<group>"; };
2D9836BF2F2939D100B184D4 /* ASMInfoRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMInfoRepository.swift; sourceTree = "<group>"; };
2DA3AE9A2E5DE6C300689F65 /* RemovedWalletNotificationsCleaner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovedWalletNotificationsCleaner.swift; sourceTree = "<group>"; };
2DA3AE9C2E5E203D00689F65 /* UpdatedWalletNotificationsCleaner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatedWalletNotificationsCleaner.swift; sourceTree = "<group>"; };
2DA4D5362EB21DC4006FCCC0 /* GiftSetupIssuesViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiftSetupIssuesViewModelFactory.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -18354,6 +18360,16 @@
path = ScamAlert;
sourceTree = "<group>";
};
2D9836AE2F2938BF00B184D4 /* AppStoreMigrationInfo */ = {
isa = PBXGroup;
children = (
2D9836AF2F2938D600B184D4 /* ASMRemoteData.swift */,
2D9836BA2F29391600B184D4 /* ASMInfoFetchOperationFactory.swift */,
2D9836BF2F2939D100B184D4 /* ASMInfoRepository.swift */,
);
path = AppStoreMigrationInfo;
sourceTree = "<group>";
};
2DA4D5302EB21DB1006FCCC0 /* Factory */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -21200,6 +21216,7 @@
84155DE8253980D700A27058 /* Services */ = {
isa = PBXGroup;
children = (
2D9836AE2F2938BF00B184D4 /* AppStoreMigrationInfo */,
2DC002ED2E8AEB6A00D49A42 /* AssetHubMigrationInfo */,
2D51D68A2E5D9F42008AB208 /* MultisigNotificationsPromo */,
2D06CC272E38F4E80093CA84 /* AppAttest */,
Expand Down Expand Up @@ -32572,6 +32589,7 @@
84FA7C1C284F8EB400B648E1 /* PendingExtrinsicInteracting.swift in Sources */,
0CD76AF62E57639A00C6E34B /* AssetModel+Orml.swift in Sources */,
84BC7043289EEF85008A9758 /* NoSigningSupportWrapper.swift in Sources */,
2D9836B02F2938D600B184D4 /* ASMRemoteData.swift in Sources */,
88A95FA828FAA99D00BE26F3 /* DAppView.swift in Sources */,
84838A3C299CD38300E030C4 /* GovernanceUndelegateValidatingParams.swift in Sources */,
AEA0C8BE2681141700F9666F /* YourValidatorList+SelectedList.swift in Sources */,
Expand Down Expand Up @@ -35517,6 +35535,7 @@
2DB5862A2EB74AB700950F7D /* GiftMapper.swift in Sources */,
641D7CF89F37B1890516015E /* ParitySignerTxScanProtocols.swift in Sources */,
84D184E82A04D5DC0060C1BD /* ScrollableContainerLayoutView.swift in Sources */,
2D9836C02F2939D100B184D4 /* ASMInfoRepository.swift in Sources */,
77A0B2F92A3CA40E00CBF653 /* StakingMoreOptionsSection.swift in Sources */,
1EE4FBB79EE6015D7D3EBDC1 /* ParitySignerTxScanWireframe.swift in Sources */,
887AFC8728BC95F0002A0422 /* MetaAccountChainResponse.swift in Sources */,
Expand Down Expand Up @@ -36281,6 +36300,7 @@
CC8C6FFB98086AFBE38BDB82 /* StakingSelectPoolInteractor.swift in Sources */,
2DEDEF3E2E4B3E8C008F07B2 /* MultisigNotificationsPresenter.swift in Sources */,
621E843DCEA85A00B419926F /* StakingSelectPoolViewController.swift in Sources */,
2D9836BB2F29391600B184D4 /* ASMInfoFetchOperationFactory.swift in Sources */,
2D5D0CD32D51422C000E32F6 /* BannersFetchOperationFactory.swift in Sources */,
A3FD763479AAB9290A612A1C /* StakingSelectPoolViewLayout.swift in Sources */,
2F73FA6B6061F343E2F033F0 /* StakingSelectPoolViewFactory.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions novawallet/Common/Configs/ApplicationConfigs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -382,5 +382,9 @@ extension ApplicationConfig: ApplicationConfigProtocol {
URL(string: "https://docs.novawallet.io/nova-wallet-wiki/wallet-management/watch-only-wallets/scam-warning")!
}

var appstoreMigrationConfigPath: String {
"https://raw.githubusercontent.com/novasamatech/nova-utils/refs/heads/master/migrations/appstore_migration/"
}

// swiftlint:enable line_length
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Foundation
import Operation_iOS

protocol ASMInfoFetchOperationFactoryProtocol {
func fetchOperation() -> BaseOperation<ASMRemoteData?>
}

final class ASMInfoFetchOperationFactory: BaseFetchOperationFactory {
private let asmConfigPath: String

init(asmConfigPath: String = ApplicationConfig.shared.appstoreMigrationConfigPath) {
self.asmConfigPath = asmConfigPath
}
}

// MARK: - Private

private extension ASMInfoFetchOperationFactory {
func createURL() -> URL? {
let path = NSString.path(withComponents: [Constants.configPath])
let urlString = URL(string: asmConfigPath)?.appendingPathComponent(path)

return urlString
}
}

// MARK: - ASMInfoFetchOperationFactoryProtocol

extension ASMInfoFetchOperationFactory: ASMInfoFetchOperationFactoryProtocol {
func fetchOperation() -> BaseOperation<ASMRemoteData?> {
guard let url = createURL() else { return .createWithError(NetworkBaseError.invalidUrl) }

return createFetchOperation(from: url)
}
}

// MARK: - Constants

private extension ASMInfoFetchOperationFactory {
enum Constants {
static var configPath: String {
#if F_RELEASE
"migrations.json"
#else
"migrations_dev.json"
#endif
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Foundation
import Operation_iOS

protocol ASMInfoRepositoryProtocol {
func fetchWrapper() -> CompoundOperationWrapper<ASMRemoteData?>
}

final class ASMInfoRepository {
private let cache: ExpiringInMemoryCache<String, ASMRemoteData>
private let fetchOperationFactory: ASMInfoFetchOperationFactoryProtocol

private let mutex = NSLock()

init(
cache: ExpiringInMemoryCache<String, ASMRemoteData> = .init(expirationPeriod: .day),
fetchOperationFactory: ASMInfoFetchOperationFactoryProtocol = ASMInfoFetchOperationFactory()
) {
self.cache = cache
self.fetchOperationFactory = fetchOperationFactory
}
}

// MARK: - Private

private extension ASMInfoRepository {
func createFetchWrapper() -> CompoundOperationWrapper<ASMRemoteData?> {
let fetchOperation = fetchOperationFactory.fetchOperation()
let cacheUpdateOperation = createCacheUpdateOperation(dependingOn: fetchOperation)

cacheUpdateOperation.addDependency(fetchOperation)

return CompoundOperationWrapper(
targetOperation: cacheUpdateOperation,
dependencies: [fetchOperation]
)
}

func createCacheUpdateOperation(
dependingOn fetchOperation: BaseOperation<ASMRemoteData?>
) -> BaseOperation<ASMRemoteData?> {
ClosureOperation { [weak self] in
let asmConfig = try fetchOperation.extractNoCancellableResultData()

if let config = asmConfig {
self?.mutex.lock()
self?.cache.store(value: config, for: config.cacheKey)
self?.mutex.unlock()
}

return asmConfig
}
}
}

// MARK: - ASMInfoRepositoryProtocol

extension ASMInfoRepository: ASMInfoRepositoryProtocol {
func fetchWrapper() -> CompoundOperationWrapper<ASMRemoteData?> {
let cachedValues = cache.fetchAllValues()

guard cachedValues.isEmpty else {
return .createWithResult(cachedValues.first)
}

return createFetchWrapper()
}
}

// MARK: - Shared

extension ASMInfoRepository {
static let shared = ASMInfoRepository()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

struct ASMRemoteData: Codable, Equatable {
let bannerPath: Banners.Domain
let wikiURL: URL
let destinationLinkData: AppLinkData
let sourceLinkData: AppLinkData
}

extension ASMRemoteData {
struct AppLinkData: Codable, Equatable {
let universalLink: URL
let urlScheme: String
}
}

extension ASMRemoteData {
var cacheKey: String {
destinationLinkData.universalLink.absoluteString
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ enum Banners {
case assets
case ahmKusama = "ahm_kusama"
case ahmPolkadot = "ahm_polkadot"
case appStoreMigration = "appstore_migration"
case appStoreMigration = "asm"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ protocol AHMInfoRepositoryProtocol {
final class AHMInfoRepository {
private let cache: ExpiringInMemoryCache<ChainModel.Id, AHMRemoteData>
private let fetchOperationFactory: AHMInfoFetchOperationFactoryProtocol
private let ahmConfigsPath: String

private let mutex = NSLock()

init(
cache: ExpiringInMemoryCache<ChainModel.Id, AHMRemoteData> = .init(expirationPeriod: .day),
fetchOperationFactory: AHMInfoFetchOperationFactoryProtocol = AHMInfoFetchOperationFactory(),
ahmConfigsPath: String = ApplicationConfig.shared.assetHubMigrationConfigsPath
) {
self.cache = cache
self.fetchOperationFactory = fetchOperationFactory
self.ahmConfigsPath = ahmConfigsPath
}
}

Expand Down
9 changes: 3 additions & 6 deletions novawallet/Modules/InfoPopup/ASM/ASMInfoViewFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import Foundation_iOS
import Keystore_iOS

struct ASMInfoPopupViewFactory {
static func createView(
learnMoreURL: URL?,
mainAction: InfoPopupAction?,
) -> InfoPopupViewProtocol? {
static func createView(info: ASMRemoteData) -> InfoPopupViewProtocol? {
let localizationManager = LocalizationManager.shared

let interactor = ASMInfoPopupInteractor(
Expand All @@ -20,8 +17,8 @@ struct ASMInfoPopupViewFactory {
interactor: interactor,
wireframe: wireframe,
viewModelFactory: viewModelFactory,
learnMoreURL: learnMoreURL,
mainAction: mainAction,
learnMoreURL: info.wikiURL,
mainAction: .url(info.destinationLinkData.universalLink),
skipAction: .custom {},
localizationManager: localizationManager
)
Expand Down
37 changes: 37 additions & 0 deletions novawallet/Modules/MainTabBar/MainTabBarInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class MainTabBarInteractor: AnyProviderAutoCleaning {
let securedLayer: SecurityLayerServiceProtocol
let inAppUpdatesService: SyncServiceProtocol

let asmInfoRepository: ASMInfoRepositoryProtocol
let notificationsPromoService: MultisigNotificationsPromoServiceProtocol
let pushScreenOpenService: PushNotificationOpenScreenFacadeProtocol
let cloudBackupMediator: CloudBackupSyncMediating
Expand All @@ -26,6 +27,7 @@ final class MainTabBarInteractor: AnyProviderAutoCleaning {
let onLaunchQueue = OnLaunchActionsQueue(
possibleActions: [
OnLaunchAction.PushNotificationsSetup(),
OnLaunchAction.ASMInfoSetup(),
OnLaunchAction.AHMInfoSetup(),
OnLaunchAction.MultisigNotificationsPromo()
]
Expand All @@ -43,6 +45,7 @@ final class MainTabBarInteractor: AnyProviderAutoCleaning {
secretImportService: SecretImportServiceProtocol,
walletMigrationService: WalletMigrationServiceProtocol,
screenOpenService: ScreenOpenServiceProtocol,
asmInfoRepository: ASMInfoRepositoryProtocol,
notificationsPromoService: MultisigNotificationsPromoServiceProtocol,
pushScreenOpenService: PushNotificationOpenScreenFacadeProtocol,
cloudBackupMediator: CloudBackupSyncMediating,
Expand All @@ -57,6 +60,7 @@ final class MainTabBarInteractor: AnyProviderAutoCleaning {
self.secretImportService = secretImportService
self.walletMigrationService = walletMigrationService
self.screenOpenService = screenOpenService
self.asmInfoRepository = asmInfoRepository
self.notificationsPromoService = notificationsPromoService
self.pushScreenOpenService = pushScreenOpenService
self.cloudBackupMediator = cloudBackupMediator
Expand Down Expand Up @@ -131,6 +135,12 @@ private extension MainTabBarInteractor {
}
}

func showAsmInfoOrNextAction() {
securedLayer.scheduleExecutionIfAuthorized { [weak self] in
self?.showAsmInfoOrNext { self?.onLaunchQueue.runNext() }
}
}

func setupNotificationPromoObserver() {
notificationsPromoService.add(
observer: self,
Expand Down Expand Up @@ -175,6 +185,29 @@ private extension MainTabBarInteractor {
}
}

func showAsmInfoOrNext(nextOnLaunchClosure: (() -> Void)? = nil) {
let wrapper = asmInfoRepository.fetchWrapper()

execute(
wrapper: wrapper,
inOperationQueue: operationQueue,
runningCallbackIn: .main
) { [weak self] result in
switch result {
case let .success(info):
guard let info else {
nextOnLaunchClosure?()
return
}
self?.presenter?.didRequestASMInfoOpen(with: info)
case let .failure(error):
self?.logger.error("Error fetching AHM info: \(error)")
}

nextOnLaunchClosure?()
}
}

func subscribeCloudSyncMonitor() {
cloudBackupMediator.subscribeSyncMonitorStatus(for: self) { [weak self] oldStatus, newStatus in
self?.securedLayer.scheduleExecutionIfAuthorized {
Expand Down Expand Up @@ -359,6 +392,10 @@ extension MainTabBarInteractor: OnLaunchActionsQueueDelegate {
func onLaunchProcessAHMInfoSetup(_: OnLaunchAction.AHMInfoSetup) {
showAhmInfoOrNextAction()
}

func onLaunchProcessASMInfoSetup(_: OnLaunchAction.ASMInfoSetup) {
showAsmInfoOrNextAction()
}
}

// MARK: - ApplicationHandlerDelegate
Expand Down
7 changes: 7 additions & 0 deletions novawallet/Modules/MainTabBar/MainTabBarPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ extension MainTabBarPresenter: MainTabBarPresenterProtocol {
}

extension MainTabBarPresenter: MainTabBarInteractorOutputProtocol {
func didRequestASMInfoOpen(with info: ASMRemoteData) {
wireframe.presentAppStoreMigrationInfoScreen(
in: view,
with: info
)
}

func didRequestAHMInfoOpen(with info: [AHMRemoteData]) {
wireframe.presentAssetHubMigrationInfoScreen(
in: view,
Expand Down
6 changes: 6 additions & 0 deletions novawallet/Modules/MainTabBar/MainTabBarProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ protocol MainTabBarInteractorOutputProtocol: AnyObject {
func didRequestPushNotificationsSetupOpen()
func didRequestMultisigNotificationsPromoOpen(with params: MultisigNotificationsPromoParams)
func didRequestAHMInfoOpen(with info: [AHMRemoteData])
func didRequestASMInfoOpen(with info: ASMRemoteData)
func didSyncCloudBackup(on purpose: CloudBackupSynсPurpose)
func didReceiveCloudSync(status: CloudBackupSyncMonitorStatus?)
}
Expand Down Expand Up @@ -90,6 +91,11 @@ protocol MainTabBarWireframeProtocol: AlertPresentable,
in view: MainTabBarViewProtocol?,
with info: [AHMRemoteData]
)

func presentAppStoreMigrationInfoScreen(
in view: MainTabBarViewProtocol?,
with info: ASMRemoteData
)
}

protocol MainTabBarViewFactoryProtocol: AnyObject {
Expand Down
Loading
Loading