From 0c8a05ea19f0da064a3b7bdabe8e3cc035b928d6 Mon Sep 17 00:00:00 2001 From: Tran Giang Long Date: Thu, 1 Feb 2024 14:40:39 +0700 Subject: [PATCH 1/3] feat(update): alert user about new update --- .../ApplicationUpdateManager.swift | 62 +++++++++++++++++++ .../ApplicationUpdateProvider.swift | 19 ++++++ .../Services/ApplicationUpdate/Version.swift | 44 +++++++++++++ .../Resolver+registerAllServices.swift | 5 ++ .../Scenes/DebugMenu/View/DebugMenuView.swift | 7 +++ .../Main/Crypto/Container/CryptoView.swift | 10 +++ .../Crypto/Container/CryptoViewModel.swift | 43 +++++++++++++ p2p_wallet/Scenes/Main/NewHome/HomeView.swift | 2 +- 8 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift create mode 100644 p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift create mode 100644 p2p_wallet/Common/Services/ApplicationUpdate/Version.swift diff --git a/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift new file mode 100644 index 0000000000..8444884cf1 --- /dev/null +++ b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift @@ -0,0 +1,62 @@ +import Combine +import Foundation + +class ApplicationUpdateManager { + enum State { + case updateAvailable(Version) + case noUpdate + } + + private let provider: ApplicationUpdateProvider + + init(provider: ApplicationUpdateProvider) { + self.provider = provider + } + + var currentInstalledVersion: Version? { + let appVersionKey = "CFBundleShortVersionString" + guard let appVersionValue = Bundle.main.object(forInfoDictionaryKey: appVersionKey) as? String else { + return nil + } + + return try? Version(from: appVersionValue) + } + + func isUpdateAvailable() async -> State { + guard + let appVersion = currentInstalledVersion, + let storeAppVersion = try? await provider.info() + else { + return .noUpdate + } + + print("[ApplicationUpdateManager]", appVersion, storeAppVersion) + + // Check + if storeAppVersion.major > appVersion.major { + return .updateAvailable(storeAppVersion) + } else if storeAppVersion.minor > appVersion.minor { + return .updateAvailable(storeAppVersion) + } else if storeAppVersion.patch > appVersion.patch { + return .updateAvailable(storeAppVersion) + } + + return .noUpdate + } + + func awareUser(version: Version) async { + UserDefaults.standard.set(version.string, forKey: "application_user_awareness") + } + + func isUserAwareAboutUpdate(version: Version) async -> Bool { + guard let userAwareness = UserDefaults.standard.object(forKey: "application_user_awareness") as? String else { + return false + } + + if version.string == userAwareness { + return true + } else { + return false + } + } +} diff --git a/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift new file mode 100644 index 0000000000..b31a778351 --- /dev/null +++ b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift @@ -0,0 +1,19 @@ +import Foundation +import Firebase + +protocol ApplicationUpdateProvider { + func info() async throws -> Version +} + +class FirebaseApplicationUpdateProvider: ApplicationUpdateProvider { + func info() async throws -> Version { + let remoteConfig = RemoteConfig.remoteConfig() + let appVersion = remoteConfig.configValue(forKey: "app_version", source: .remote).stringValue + + guard let appVersion else { + throw Version.VersionError.invalidFormat + } + + return try Version(from: appVersion) + } +} diff --git a/p2p_wallet/Common/Services/ApplicationUpdate/Version.swift b/p2p_wallet/Common/Services/ApplicationUpdate/Version.swift new file mode 100644 index 0000000000..d8e7c7b58d --- /dev/null +++ b/p2p_wallet/Common/Services/ApplicationUpdate/Version.swift @@ -0,0 +1,44 @@ +import Foundation + +struct Version: Decodable { + // MARK: - Enumerations + enum VersionError: Error { + case invalidFormat + } + + // MARK: - Public properties + let major: Int + let minor: Int + let patch: Int + + // MARK: - Init + init(from decoder: Decoder) throws { + do { + let container = try decoder.singleValueContainer() + let version = try container.decode(String.self) + try self.init(from: version) + } catch { + throw VersionError.invalidFormat + } + } + + init(from version: String) throws { + let versionComponents = version.components(separatedBy: ".").map { Int($0) } + guard versionComponents.count == 3 else { + throw VersionError.invalidFormat + } + + guard let major = versionComponents[0], let minor = versionComponents[1], + let patch = versionComponents[2] else { + throw VersionError.invalidFormat + } + + self.major = major + self.minor = minor + self.patch = patch + } + + var string: String { + "\(major).\(minor).\(patch)" + } +} diff --git a/p2p_wallet/Injection/Resolver+registerAllServices.swift b/p2p_wallet/Injection/Resolver+registerAllServices.swift index c3e78d2dd6..9bd050c967 100644 --- a/p2p_wallet/Injection/Resolver+registerAllServices.swift +++ b/p2p_wallet/Injection/Resolver+registerAllServices.swift @@ -80,6 +80,11 @@ extension Resolver: ResolverRegistering { .implements(KeyAppTokenProvider.self) .scope(.application) + register { + ApplicationUpdateManager(provider: FirebaseApplicationUpdateProvider()) + } + .scope(.application) + register { DeviceShareMigrationService( isWeb3AuthUser: resolve(UserWalletManager.self) diff --git a/p2p_wallet/Scenes/DebugMenu/View/DebugMenuView.swift b/p2p_wallet/Scenes/DebugMenu/View/DebugMenuView.swift index b21574904b..eade4b0839 100644 --- a/p2p_wallet/Scenes/DebugMenu/View/DebugMenuView.swift +++ b/p2p_wallet/Scenes/DebugMenu/View/DebugMenuView.swift @@ -81,6 +81,13 @@ struct DebugMenuView: View { DebugTextField(title: "Bridge:", content: $globalAppState.bridgeEndpoint) DebugTextField(title: "Token:", content: $globalAppState.tokenEndpoint) Toggle("Prefer direct swap", isOn: $globalAppState.preferDirectSwap) + + Button { + Task { + UserDefaults.standard.set(nil, forKey: "application_user_awareness") + } + } label: { Text("Clean version alert") } + Button { Task { ResolverScope.session.reset() diff --git a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift index b935a32969..dfe4215cd6 100644 --- a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift +++ b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift @@ -76,5 +76,15 @@ struct CryptoView: View { .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in viewModel.viewAppeared() } + .alert("Update available", isPresented: $viewModel.updateAlert) { + Button("Update", action: { + viewModel.openAppstore() + viewModel.userIsAwareAboutUpdate() + }) + Button("Cancel", role: .cancel, action: { + viewModel.userIsAwareAboutUpdate() + }) + } + } } diff --git a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoViewModel.swift b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoViewModel.swift index 1af470d5ed..df38a8fa2d 100644 --- a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoViewModel.swift +++ b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoViewModel.swift @@ -7,12 +7,14 @@ import Resolver import Sell import Send import SolanaSwift +import UIKit import Wormhole /// ViewModel of `Crypto` scene final class CryptoViewModel: BaseViewModel, ObservableObject { // MARK: - Dependencies + @Injected private var authenticationHandler: AuthenticationHandlerType @Injected private var solanaAccountsService: SolanaAccountsService @Injected private var ethereumAccountsService: EthereumAccountsService @Injected private var analyticsManager: AnalyticsManager @@ -23,6 +25,7 @@ final class CryptoViewModel: BaseViewModel, ObservableObject { @Injected private var nameStorage: NameStorageType @Injected private var sellDataService: any SellDataService @Injected private var createNameService: CreateNameService + @Injected private var applicationUpdateManager: ApplicationUpdateManager let navigation: PassthroughSubject @@ -31,6 +34,9 @@ final class CryptoViewModel: BaseViewModel, ObservableObject { @Published var state = State.pending @Published var address = "" + @Published var updateAlert: Bool = false + @Published var newVersion: Version? + // MARK: - Initializers init(navigation: PassthroughSubject) { @@ -50,6 +56,19 @@ final class CryptoViewModel: BaseViewModel, ObservableObject { await CryptoAccountsSynchronizationService().refresh() } + func userIsAwareAboutUpdate() { + guard let version = newVersion else { return } + Task { await applicationUpdateManager.awareUser(version: version) } + } + + func openAppstore() { + UIApplication.shared.open( + URL(string: "itms-apps://itunes.apple.com/app/id1605603333")!, + options: [:], + completionHandler: nil + ) + } + func viewAppeared() { if available(.solanaNegativeStatus) { solanaTracker.startTracking() @@ -175,6 +194,30 @@ private extension CryptoViewModel { self?.updateAddressIfNeeded() } .store(in: &subscriptions) + + // Update + authenticationHandler.authenticationStatusPublisher + .filter { $0 == nil } + .delay(for: 3, scheduler: RunLoop.main) + .sink { [weak self] _ in + Task { + guard let self else { return } + let result = await self.applicationUpdateManager.isUpdateAvailable() + switch result { + case .noUpdate: + return + case let .updateAvailable(version): + if await self.applicationUpdateManager.isUserAwareAboutUpdate(version: version) { + return + } else { + await MainActor.run { + self.updateAlert = true + self.newVersion = version + } + } + } + } + }.store(in: &subscriptions) } } diff --git a/p2p_wallet/Scenes/Main/NewHome/HomeView.swift b/p2p_wallet/Scenes/Main/NewHome/HomeView.swift index d9bdb60cb2..3833039079 100644 --- a/p2p_wallet/Scenes/Main/NewHome/HomeView.swift +++ b/p2p_wallet/Scenes/Main/NewHome/HomeView.swift @@ -38,7 +38,7 @@ struct HomeView: View { } } - func navigation(@ViewBuilder content: @escaping () -> Content) -> some View { + func navigation(@ViewBuilder content: @escaping () -> some View) -> some View { NavigationView { ZStack { Color(.smoke) From 91f3a827a95680c144745f3f0c3462168e122272 Mon Sep 17 00:00:00 2001 From: runner Date: Thu, 1 Feb 2024 07:43:17 +0000 Subject: [PATCH 2/3] fix(swiftformat): Apply Swiftformat changes --- .../ApplicationUpdate/ApplicationUpdateManager.swift | 2 +- .../ApplicationUpdate/ApplicationUpdateProvider.swift | 6 +++--- .../Common/Services/ApplicationUpdate/Version.swift | 10 +++++++--- .../Scenes/Main/Crypto/Container/CryptoView.swift | 1 - 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift index 8444884cf1..93821955cf 100644 --- a/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift +++ b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateManager.swift @@ -29,7 +29,7 @@ class ApplicationUpdateManager { else { return .noUpdate } - + print("[ApplicationUpdateManager]", appVersion, storeAppVersion) // Check diff --git a/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift index b31a778351..3d147a0051 100644 --- a/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift +++ b/p2p_wallet/Common/Services/ApplicationUpdate/ApplicationUpdateProvider.swift @@ -1,5 +1,5 @@ -import Foundation import Firebase +import Foundation protocol ApplicationUpdateProvider { func info() async throws -> Version @@ -9,11 +9,11 @@ class FirebaseApplicationUpdateProvider: ApplicationUpdateProvider { func info() async throws -> Version { let remoteConfig = RemoteConfig.remoteConfig() let appVersion = remoteConfig.configValue(forKey: "app_version", source: .remote).stringValue - + guard let appVersion else { throw Version.VersionError.invalidFormat } - + return try Version(from: appVersion) } } diff --git a/p2p_wallet/Common/Services/ApplicationUpdate/Version.swift b/p2p_wallet/Common/Services/ApplicationUpdate/Version.swift index d8e7c7b58d..69fd8e7f96 100644 --- a/p2p_wallet/Common/Services/ApplicationUpdate/Version.swift +++ b/p2p_wallet/Common/Services/ApplicationUpdate/Version.swift @@ -2,16 +2,19 @@ import Foundation struct Version: Decodable { // MARK: - Enumerations + enum VersionError: Error { case invalidFormat } // MARK: - Public properties + let major: Int let minor: Int let patch: Int // MARK: - Init + init(from decoder: Decoder) throws { do { let container = try decoder.singleValueContainer() @@ -29,15 +32,16 @@ struct Version: Decodable { } guard let major = versionComponents[0], let minor = versionComponents[1], - let patch = versionComponents[2] else { - throw VersionError.invalidFormat + let patch = versionComponents[2] + else { + throw VersionError.invalidFormat } self.major = major self.minor = minor self.patch = patch } - + var string: String { "\(major).\(minor).\(patch)" } diff --git a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift index dfe4215cd6..6393ceeba8 100644 --- a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift +++ b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift @@ -85,6 +85,5 @@ struct CryptoView: View { viewModel.userIsAwareAboutUpdate() }) } - } } From 053e474be42f56f28dec25365337d5c0b5fd61ac Mon Sep 17 00:00:00 2001 From: Tran Giang Long Date: Mon, 5 Feb 2024 16:10:56 +0700 Subject: [PATCH 3/3] fix: localized --- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- p2p_wallet/Resources/Base.lproj/Localizable.strings | 1 + p2p_wallet/Resources/en.lproj/Localizable.strings | 1 + p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift | 6 +++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0fe2c1e0af..48b9ba0736 100644 --- a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -266,8 +266,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ashleymills/Reachability.swift.git", "state" : { - "revision" : "c01bbdf2d633cf049ae1ed1a68a2020a8bda32e2", - "version" : "5.1.0" + "revision" : "c01127cb51f591045696128effe43c16840d08bf", + "version" : "5.2.0" } }, { @@ -392,8 +392,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "8e68404f641300bfd0e37d478683bb275926760c", - "version" : "1.15.2" + "revision" : "e7b77228b34057041374ebef00c0fd7739d71a2b", + "version" : "1.15.3" } }, { diff --git a/p2p_wallet/Resources/Base.lproj/Localizable.strings b/p2p_wallet/Resources/Base.lproj/Localizable.strings index 34ea753e12..99d5435473 100644 --- a/p2p_wallet/Resources/Base.lproj/Localizable.strings +++ b/p2p_wallet/Resources/Base.lproj/Localizable.strings @@ -597,3 +597,4 @@ "Token 2022 transfer fee" = "Token 2022 transfer fee"; "Calculated by subtracting the token 2022 transfer fee from your balance" = "Calculated by subtracting the token 2022 transfer fee from your balance"; "Calculated by subtracting the token 2022 transfer fee and account creation fee from your balance" = "Calculated by subtracting the token 2022 transfer fee and account creation fee from your balance"; +"Update available" = "Update available"; diff --git a/p2p_wallet/Resources/en.lproj/Localizable.strings b/p2p_wallet/Resources/en.lproj/Localizable.strings index 1b6c0248aa..08931fd2df 100644 --- a/p2p_wallet/Resources/en.lproj/Localizable.strings +++ b/p2p_wallet/Resources/en.lproj/Localizable.strings @@ -585,3 +585,4 @@ "Token 2022 transfer fee" = "Token 2022 transfer fee"; "Calculated by subtracting the token 2022 transfer fee from your balance" = "Calculated by subtracting the token 2022 transfer fee from your balance"; "Calculated by subtracting the token 2022 transfer fee and account creation fee from your balance" = "Calculated by subtracting the token 2022 transfer fee and account creation fee from your balance"; +"Update available" = "Update available"; diff --git a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift index dfe4215cd6..d77bcc5292 100644 --- a/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift +++ b/p2p_wallet/Scenes/Main/Crypto/Container/CryptoView.swift @@ -76,12 +76,12 @@ struct CryptoView: View { .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in viewModel.viewAppeared() } - .alert("Update available", isPresented: $viewModel.updateAlert) { - Button("Update", action: { + .alert(L10n.updateAvailable, isPresented: $viewModel.updateAlert) { + Button(L10n.update, action: { viewModel.openAppstore() viewModel.userIsAwareAboutUpdate() }) - Button("Cancel", role: .cancel, action: { + Button(L10n.cancel, role: .cancel, action: { viewModel.userIsAwareAboutUpdate() }) }