Skip to content

Commit

Permalink
MOB-2119 - Maintenance mode (#616)
Browse files Browse the repository at this point in the history
* Added key for full maintenance mode.
Handle showing of dedicated view.

* Implemented UI for full maintenance mode view

* Extended structure of maintenance data

* Notify about flag updated
Handle flag updated in maintenance view

* Created ud feature flag tracker object

* Refactoring

* Added flag to track OKLink availability

* Handle profiles API service is down

* Handle Ecomm API service is down

* Handle attempt to open domain profile when profiles api services is down

* Handle Infura service is down

* Handle MPC service is down

* Restrict mpc purchase when ecomm is in maintenance

* Enhanced maintenance mode data.
Added tests.

* Improved full maintenance handling

* Handle maintenance status updated in flag tracker
  • Loading branch information
Oleg-Pecheneg authored Jul 24, 2024
1 parent 77737a5 commit 9780100
Show file tree
Hide file tree
Showing 40 changed files with 1,111 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ final class CoreAppCoordinator: CoreAppCoordinatorProtocol {

}

func showFullMaintenanceModeOn(maintenanceData: MaintenanceModeData) { }

func setKeyWindow() {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@
//

import Foundation
import Combine

final class UDFeatureFlagsService: UDFeatureFlagsServiceProtocol {
private(set) var featureFlagPublisher = PassthroughSubject<UDFeatureFlag, Never>()

func entityValueFor<T: Codable>(flag: UDFeatureFlag) -> T? {
nil
}

func valueFor(flag: UDFeatureFlag) -> Bool {
true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ extension FB_UD_MPC {
.init(id: "1", status: "", type: "")
}

func startSendETHTransaction(accessToken: String, accountId: String, assetId: String, destinationAddress: String, data: String, value: String) async throws -> FB_UD_MPC.OperationDetails {
.init(id: "1", status: "", type: "")
}

func waitForOperationCompleted(accessToken: String, operationId: String) async throws {

}
Expand All @@ -56,7 +60,6 @@ extension FB_UD_MPC {
""
}


func fetchCryptoPortfolioForMPC(wallet: String, accessToken: String) async throws -> [WalletTokenPortfolio] {
MockEntitiesFabric.Wallet.mockEntities()[0].balance
}
Expand Down
60 changes: 60 additions & 0 deletions unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ final class GeneralAppContext: AppContextProtocol {
private(set) lazy var appLaunchService: AppLaunchServiceProtocol = {
AppLaunchService(coreAppCoordinator: coreAppCoordinator,
udWalletsService: udWalletsService,
userProfilesService: userProfilesService)
userProfilesService: userProfilesService,
udFeatureFlagsService: udFeatureFlagsService)
}()
private(set) lazy var domainRecordsService: DomainRecordsServiceProtocol = DomainRecordsService()
private(set) lazy var qrCodeService: QRCodeServiceProtocol = QRCodeService()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// MaintenanceModeData.swift
// domains-manager-ios
//
// Created by Oleg Kuplin on 23.07.2024.
//

import Foundation

struct MaintenanceModeData: Codable {
private let isOn: Bool
var link: String?
var title: String?
var message: String?
var startDate: Date?
var endDate: Date?

init(isOn: Bool,
link: String? = nil,
title: String? = nil,
message: String? = nil,
startDate: Date? = nil,
endDate: Date? = nil) {
self.isOn = isOn
self.link = link
self.title = title
self.message = message
self.startDate = startDate
self.endDate = endDate
}

var isCurrentlyEnabled: Bool {
if isOn {
/// If there's a startDate set, we check if it is already started,. Otherwise return true
if let startDate {
if startDate > Date() {
return false
} else if let endDate { /// If there's end date, we check if it is already ended. Otherwise return true
return endDate >= Date()
}
return true
} else if let endDate {
return endDate >= Date()
}

return true
}
return false
}

var linkURL: URL? { URL(string: link ?? "") }

func onMaintenanceStatusUpdate(callback: @escaping EmptyCallback) {
let now = Date()

func scheduleUpdatedAfter(date: Date) {
let timeInterval = date.timeIntervalSince(now) + 1
DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval) {
callback()
}
}

if let startDate,
startDate > now {
scheduleUpdatedAfter(date: startDate)
} else if let endDate,
endDate > now {
scheduleUpdatedAfter(date: endDate)
}
}
}




Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,26 @@ extension String {
static let backedUp = "BACKED_UP"
static let backUp = "BACK_UP"
static let setAsPrimaryDomain = "SET_AS_PRIMARY_DOMAIN"

// Maintenance
static let fullMaintenanceMessageTitle = "FULL_MAINTENANCE_MESSAGE_TITLE"
static let fullMaintenanceMessageSubtitle = "FULL_MAINTENANCE_MESSAGE_SUBTITLE"
static let activityMaintenanceMessageTitle = "ACTIVITY_MAINTENANCE_MESSAGE_TITLE"
static let activityMaintenanceMessageSubtitle = "ACTIVITY_MAINTENANCE_MESSAGE_SUBTITLE"
static let exploreMaintenanceMessageTitle = "EXPLORE_MAINTENANCE_MESSAGE_TITLE"
static let exploreMaintenanceMessageSubtitle = "EXPLORE_MAINTENANCE_MESSAGE_SUBTITLE"
static let homeMaintenanceMessageTitle = "HOME_MAINTENANCE_MESSAGE_TITLE"
static let homeMaintenanceMessageSubtitle = "HOME_MAINTENANCE_MESSAGE_SUBTITLE"
static let purchaseDomainsMaintenanceMessageTitle = "PURCHASE_DOMAINS_MAINTENANCE_MESSAGE_TITLE"
static let purchaseDomainsMaintenanceMessageSubtitle = "PURCHASE_DOMAINS_MAINTENANCE_MESSAGE_SUBTITLE"
static let vaultedDomainsMaintenanceMessageTitle = "VAULTED_DOMAINS_MAINTENANCE_MESSAGE_TITLE"
static let vaultedDomainsMaintenanceMessageSubtitle = "VAULTED_DOMAINS_MAINTENANCE_MESSAGE_SUBTITLE"
static let domainProfileMaintenanceMessageTitle = "DOMAIN_PROFILE_MAINTENANCE_MESSAGE_TITLE"
static let domainProfileMaintenanceMessageSubtitle = "DOMAIN_PROFILE_MAINTENANCE_MESSAGE_SUBTITLE"
static let sendCryptoMaintenanceMessageTitle = "SEND_CRYPTO_MAINTENANCE_MESSAGE_TITLE"
static let sendCryptoMaintenanceMessageSubtitle = "SEND_CRYPTO_MAINTENANCE_MESSAGE_SUBTITLE"
static let signMessagesMaintenanceMessageTitle = "SIGN_MESSAGES_MAINTENANCE_MESSAGE_TITLE"
static let signMessagesMaintenanceMessageSubtitle = "SIGN_MESSAGES_MAINTENANCE_MESSAGE_SUBTITLE"
}

enum SystemImage: String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ struct HomeActivityView: View, ViewAnalyticsLogger {

@EnvironmentObject var tabRouter: HomeTabRouter
@State private var navigationState: NavigationStateManager?
@StateObject private var okLinkFlagTracker = UDMaintenanceModeFeatureFlagTracker(featureFlag: .isMaintenanceOKLinkEnabled)
@StateObject private var profilesAPIFlagTracker = UDMaintenanceModeFeatureFlagTracker(featureFlag: .isMaintenanceProfilesAPIEnabled)
@StateObject var viewModel: HomeActivityViewModel

var isOtherScreenPushed: Bool { !tabRouter.activityTabNavPath.isEmpty }
var analyticsName: Analytics.ViewName { .homeActivity }

Expand Down Expand Up @@ -94,7 +96,15 @@ private extension HomeActivityView {
private extension HomeActivityView {
@ViewBuilder
func contentList() -> some View {
if viewModel.groupedTxs.isEmpty,
if okLinkFlagTracker.maintenanceData?.isCurrentlyEnabled == true {
MaintenanceDetailsEmbeddedView(serviceType: .activity,
maintenanceData: okLinkFlagTracker.maintenanceData)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if profilesAPIFlagTracker.maintenanceData?.isCurrentlyEnabled == true {
MaintenanceDetailsEmbeddedView(serviceType: .activity,
maintenanceData: profilesAPIFlagTracker.maintenanceData)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if viewModel.groupedTxs.isEmpty,
!viewModel.isLoadingMore {
GeometryReader { geometry in
/// ScrollView needed to keep PTR functionality
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ struct HomeExploreView: View, ViewAnalyticsLogger {

@EnvironmentObject var tabRouter: HomeTabRouter
@State private var navigationState: NavigationStateManager?
@StateObject private var profilesAPIFlagTracker = UDMaintenanceModeFeatureFlagTracker(featureFlag: .isMaintenanceProfilesAPIEnabled)
@StateObject var viewModel: HomeExploreViewModel

var isOtherScreenPushed: Bool { !tabRouter.exploreTabNavPath.isEmpty }
var analyticsName: Analytics.ViewName { .homeExplore }

Expand All @@ -22,7 +23,7 @@ struct HomeExploreView: View, ViewAnalyticsLogger {
if viewModel.isSearchActive {
domainSearchTypeSelector()
}
contentList()
contentView()
}
.animation(.default, value: UUID())
.background(Color.backgroundDefault)
Expand Down Expand Up @@ -122,6 +123,17 @@ private extension HomeExploreView {
.background(.regularMaterial)
}

@ViewBuilder
func contentView() -> some View {
if profilesAPIFlagTracker.maintenanceData?.isCurrentlyEnabled == true {
MaintenanceDetailsEmbeddedView(serviceType: .explore,
maintenanceData: profilesAPIFlagTracker.maintenanceData)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
contentList()
}
}

@ViewBuilder
func contentList() -> some View {
List {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ final class HomeTabRouter: ObservableObject {
@Published var exploreTabNavPath: [HomeExploreNavigationDestination] = []
@Published var activityTabNavPath: [HomeActivityNavigationDestination] = []
@Published var presentedNFT: NFTDisplayInfo?

@Published var presentedDomain: DomainPresentationDetails?
@Published var presentedPublicDomain: PublicProfileViewConfiguration?
@Published var presentedUBTSearch: UBTSearchPresentationDetails?
Expand Down Expand Up @@ -162,6 +163,10 @@ extension HomeTabRouter {
guard !domain.isTransferring else {
showDomainTransferringInProgress(domain)
return }
let flagTracker = UDMaintenanceModeFeatureFlagTracker(featureFlag: .isMaintenanceProfilesAPIEnabled)
guard flagTracker.maintenanceData?.isCurrentlyEnabled != true else {
appContext.pullUpViewService.showDomainProfileInMaintenancePullUp(in: topVC)
return }

presentedDomain = .init(domain: domain,
wallet: wallet,
Expand Down Expand Up @@ -213,8 +218,8 @@ extension HomeTabRouter {
let mintedDomains = domains.interactableItems()

walletViewNavPath.append(HomeWalletNavigationDestination.minting(mode: mode,
mintedDomains: mintedDomains,
domainsMintedCallback: { result in
mintedDomains: mintedDomains,
domainsMintedCallback: { result in
}, mintingNavProvider: { [weak self] mintingNav in
self?.mintingNav = mintingNav
}))
Expand Down Expand Up @@ -267,7 +272,6 @@ extension HomeTabRouter {
completion(Void())
}))
}
//await view.dismissPullUpMenu()
await finishSetupPurchasedProfileIfNeeded(domains: domains, requests: requests)
}
}
Expand Down Expand Up @@ -337,6 +341,12 @@ extension HomeTabRouter {
}
}
}

func showSigningMessagesInMaintenancePullUp() {
guard let topVC else { return }

appContext.pullUpViewService.showMessageSigningInMaintenancePullUp(in: topVC)
}
}

// MARK: - Pull up related
Expand Down Expand Up @@ -416,7 +426,6 @@ extension HomeTabRouter: PublicProfileViewDelegate {

UDRouter().showTransferInProgressScreen(domain: domain, in: topVC)
}

}

// MARK: - Private methods
Expand Down Expand Up @@ -501,7 +510,6 @@ private extension HomeTabRouter {
}
}))
}
// await view.dismissPullUpMenu()
await finishSetupPurchasedProfileIfNeeded(domains: domains, requests: requests)
} catch {
PurchasedDomainsStorage.setPendingNonEmptyProfiles(pendingProfilesLeft)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,13 @@ struct SendCryptoAssetRootView: View {

@Environment(\.presentationMode) private var presentationMode
@StateObject var viewModel: SendCryptoAssetViewModel
@StateObject private var infuraFlagTracker = UDMaintenanceModeFeatureFlagTracker(featureFlag: .isMaintenanceInfuraEnabled)
@StateObject private var mpcFlagTracker = UDMaintenanceModeFeatureFlagTracker(featureFlag: .isMaintenanceMPCEnabled)

var body: some View {
NavigationViewWithCustomTitle(content: {
ZStack {
SendCryptoAssetSelectReceiverView()
.environmentObject(viewModel)
.navigationBarTitleDisplayMode(.inline)
.navigationDestination(for: SendCryptoAsset.NavigationDestination.self) { destination in
SendCryptoAsset.LinkNavigationDestination.viewFor(navigationDestination: destination)
.ignoresSafeArea()
.environmentObject(viewModel)
}
.onChange(of: viewModel.navPath) { _ in
updateTitleView()
}
.trackNavigationControllerEvents(onDidNotFinishNavigationBack: updateTitleView)

contentView()
if viewModel.isLoading {
ProgressView()
}
Expand All @@ -52,6 +42,63 @@ private extension SendCryptoAssetRootView {
}
}

// MARK: - Private methods
private extension SendCryptoAssetRootView {
var isMaintenanceOnForSelectedWallet: Bool {
affectedServiceMaintenanceData?.isCurrentlyEnabled == true
}

var affectedServiceMaintenanceData: MaintenanceModeData? {
switch viewModel.sourceWallet.udWallet.type {
case .mpc:
return mpcFlagTracker.maintenanceData
case .externalLinked:
return nil
default:
return infuraFlagTracker.maintenanceData
}
}

@ViewBuilder
func contentView() -> some View {
if isMaintenanceOnForSelectedWallet {
MaintenanceDetailsFullView(serviceType: .sendCrypto,
maintenanceData: affectedServiceMaintenanceData)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
closeButton()
}
}
} else {
sendCryptoFlowView()
}
}

@ViewBuilder
func sendCryptoFlowView() -> some View {
SendCryptoAssetSelectReceiverView()
.environmentObject(viewModel)
.navigationBarTitleDisplayMode(.inline)
.navigationDestination(for: SendCryptoAsset.NavigationDestination.self) { destination in
SendCryptoAsset.LinkNavigationDestination.viewFor(navigationDestination: destination)
.ignoresSafeArea()
.environmentObject(viewModel)
}
.onChange(of: viewModel.navPath) { _ in
updateTitleView()
}
.trackNavigationControllerEvents(onDidNotFinishNavigationBack: updateTitleView)
}

@ViewBuilder
func closeButton() -> some View {
CloseButtonView {
presentationMode.wrappedValue.dismiss()
}
}
}

#Preview {
PresentAsModalPreviewView {
SendCryptoAssetRootView(viewModel: SendCryptoAssetViewModel(initialData: .init(sourceWallet: MockEntitiesFabric.Wallet.mockEntities()[0])))
Expand Down
Loading

0 comments on commit 9780100

Please sign in to comment.