diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 6c2c353423..8419de7e2a 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -3002,6 +3002,10 @@ D3384D222BFF0CCA00515664 /* MarketVolumeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D202BFF0CCA00515664 /* MarketVolumeView.swift */; }; D3384D242BFF0CD100515664 /* MarketVolumeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D232BFF0CD100515664 /* MarketVolumeViewModel.swift */; }; D3384D252BFF0CD100515664 /* MarketVolumeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D232BFF0CD100515664 /* MarketVolumeViewModel.swift */; }; + D3384D4E2C07020300515664 /* PriceChangeMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D4D2C07020300515664 /* PriceChangeMode.swift */; }; + D3384D4F2C07020300515664 /* PriceChangeMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D4D2C07020300515664 /* PriceChangeMode.swift */; }; + D3384D512C0703B400515664 /* PriceChangeModeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D502C0703B400515664 /* PriceChangeModeManager.swift */; }; + D3384D522C0703B400515664 /* PriceChangeModeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D502C0703B400515664 /* PriceChangeModeManager.swift */; }; D339A93D29126D0F00B895BE /* HsCryptoKit in Frameworks */ = {isa = PBXBuildFile; productRef = D339A93C29126D0F00B895BE /* HsCryptoKit */; }; D339A93F29126D2A00B895BE /* HsCryptoKit in Frameworks */ = {isa = PBXBuildFile; productRef = D339A93E29126D2A00B895BE /* HsCryptoKit */; }; D3402AEE2BF5D58B003BF6F8 /* WatchlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3402AED2BF5D58B003BF6F8 /* WatchlistViewModel.swift */; }; @@ -4940,6 +4944,8 @@ D3384D1C2BFF0CB800515664 /* MarketMarketCapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketMarketCapViewModel.swift; sourceTree = ""; }; D3384D202BFF0CCA00515664 /* MarketVolumeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketVolumeView.swift; sourceTree = ""; }; D3384D232BFF0CD100515664 /* MarketVolumeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketVolumeViewModel.swift; sourceTree = ""; }; + D3384D4D2C07020300515664 /* PriceChangeMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceChangeMode.swift; sourceTree = ""; }; + D3384D502C0703B400515664 /* PriceChangeModeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceChangeModeManager.swift; sourceTree = ""; }; D3402AED2BF5D58B003BF6F8 /* WatchlistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistViewModel.swift; sourceTree = ""; }; D3402AF02BF5D59D003BF6F8 /* WatchlistModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistModifier.swift; sourceTree = ""; }; D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistManager.swift; sourceTree = ""; }; @@ -5807,6 +5813,7 @@ 11B359980AA45D6B44151D7A /* StatManager.swift */, 6BB14F6A2BF49E7100E879B2 /* WalletButtonHiddenManager.swift */, D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */, + D3384D502C0703B400515664 /* PriceChangeModeManager.swift */, ); path = Managers; sourceTree = ""; @@ -6194,6 +6201,7 @@ 11B35B9F4421EE65B8B09370 /* StatRecord.swift */, D36E50832BF75B6900C361BD /* WatchlistTimePeriod.swift */, D34A29B52BFB4E3200F63036 /* WatchlistSortBy.swift */, + D3384D4D2C07020300515664 /* PriceChangeMode.swift */, ); path = Models; sourceTree = ""; @@ -10414,6 +10422,7 @@ 11B3555B8D452B7F64815FAC /* WalletStorage.swift in Sources */, 11B35205EDD1A11067E1AC91 /* CoinManager.swift in Sources */, 11B35FB28152F8881369DD9D /* AdapterManager.swift in Sources */, + D3384D522C0703B400515664 /* PriceChangeModeManager.swift in Sources */, 11B350388CD7F33B10BD3F4B /* AdapterFactory.swift in Sources */, D02A67D2272A7460009B2C1C /* TweetsPageResponse.swift in Sources */, 11B358362F756E91646878D0 /* CoinValue.swift in Sources */, @@ -11281,6 +11290,7 @@ ABC9A5361D9712C95F456376 /* Extensions.swift in Sources */, D3DD672C2BC3BF5200EC7F78 /* OneInchMultiSwapConfirmationQuote.swift in Sources */, ABC9A50D63AD21802AF5DE22 /* BaseAnimation.swift in Sources */, + D3384D4F2C07020300515664 /* PriceChangeMode.swift in Sources */, ABC9A8F221BB603CA4EBFA1D /* AlphaDismissAnimation.swift in Sources */, ABC9A724798E2748EAC06A36 /* AlphaPresentAnimation.swift in Sources */, ABC9A1FD7594369D1F36C4EB /* MovingDismissAnimation.swift in Sources */, @@ -11990,6 +12000,7 @@ 11B35E4FE3117F6B681F6748 /* Wallet.swift in Sources */, 11B35CAD5A7E0C8709559FD2 /* WalletManager.swift in Sources */, 11B35D80D1A22BA2EB8F31B8 /* WalletStorage.swift in Sources */, + D3384D512C0703B400515664 /* PriceChangeModeManager.swift in Sources */, 11B355A29CDAF16148F1C546 /* CoinManager.swift in Sources */, D36DE0B0272FD689000BC916 /* SwapModule.swift in Sources */, 11B3580B9C21B55ACC07B043 /* AdapterManager.swift in Sources */, @@ -12857,6 +12868,7 @@ 11B35DCCBE18D2F1F16C01C5 /* PlaceholderViewNew.swift in Sources */, D3DD672B2BC3BF5200EC7F78 /* OneInchMultiSwapConfirmationQuote.swift in Sources */, 11B35ED9F3C0EA3CCC4C0FF4 /* SyncErrorView.swift in Sources */, + D3384D4E2C07020300515664 /* PriceChangeMode.swift in Sources */, ABC9AE362775FBF83C15C231 /* Extensions.swift in Sources */, ABC9A1109D59CD5F71FBC153 /* BaseAnimation.swift in Sources */, ABC9A9A71EA9758D096AAA68 /* AlphaDismissAnimation.swift in Sources */, diff --git a/UnstoppableWallet/UnstoppableWallet/Core/App.swift b/UnstoppableWallet/UnstoppableWallet/Core/App.swift index 3b511c43d1..b6b82550e1 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/App.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/App.swift @@ -40,6 +40,7 @@ class App { let balanceHiddenManager: BalanceHiddenManager let balanceConversionManager: BalanceConversionManager let walletButtonHiddenManager: WalletButtonHiddenManager + let priceChangeModeManager: PriceChangeModeManager let appVersionStorage: AppVersionStorage let appVersionManager: AppVersionManager @@ -141,6 +142,7 @@ class App { balanceHiddenManager = BalanceHiddenManager(userDefaultsStorage: userDefaultsStorage) balanceConversionManager = BalanceConversionManager(marketKit: marketKit, userDefaultsStorage: userDefaultsStorage) walletButtonHiddenManager = WalletButtonHiddenManager(userDefaultsStorage: userDefaultsStorage) + priceChangeModeManager = PriceChangeModeManager(userDefaultsStorage: userDefaultsStorage) let appVersionRecordStorage = AppVersionRecordStorage(dbPool: dbPool) appVersionStorage = AppVersionStorage(storage: appVersionRecordStorage) diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/BalanceConversionManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/BalanceConversionManager.swift index de93dfb898..09e45898fe 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/BalanceConversionManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/BalanceConversionManager.swift @@ -1,6 +1,5 @@ +import HsExtensions import MarketKit -import RxRelay -import RxSwift class BalanceConversionManager { private let tokenQueries = [ @@ -15,10 +14,8 @@ class BalanceConversionManager { let conversionTokens: [Token] - private let conversionTokenRelay = PublishRelay() - private(set) var conversionToken: Token? { + @PostPublished private(set) var conversionToken: Token? { didSet { - conversionTokenRelay.accept(conversionToken) userDefaultsStorage.set(value: conversionToken?.blockchain.uid, for: keyBlockchainUid) } } @@ -48,10 +45,6 @@ class BalanceConversionManager { } extension BalanceConversionManager { - var conversionTokenObservable: Observable { - conversionTokenRelay.asObservable() - } - func toggleConversionToken() { guard conversionTokens.count > 1, let conversionToken else { return @@ -63,6 +56,10 @@ extension BalanceConversionManager { } func set(conversionToken: Token?) { + guard self.conversionToken != conversionToken else { + return + } + self.conversionToken = conversionToken } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/PriceChangeModeManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/PriceChangeModeManager.swift new file mode 100644 index 0000000000..a5dbf40316 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/PriceChangeModeManager.swift @@ -0,0 +1,24 @@ +import Combine +import HsExtensions + +class PriceChangeModeManager { + private let keyPriceChangeMode = "price-change-mode" + + private let userDefaultsStorage: UserDefaultsStorage + + @PostPublished var priceChangeMode: PriceChangeMode { + didSet { + userDefaultsStorage.set(value: priceChangeMode.rawValue, for: keyPriceChangeMode) + } + } + + init(userDefaultsStorage: UserDefaultsStorage) { + self.userDefaultsStorage = userDefaultsStorage + + if let rawValue: String = userDefaultsStorage.value(for: keyPriceChangeMode), let value = PriceChangeMode(rawValue: rawValue) { + priceChangeMode = value + } else { + priceChangeMode = .hour24 + } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Models/BalancePrimaryValue.swift b/UnstoppableWallet/UnstoppableWallet/Models/BalancePrimaryValue.swift index f29ad54ddf..34ec715af8 100644 --- a/UnstoppableWallet/UnstoppableWallet/Models/BalancePrimaryValue.swift +++ b/UnstoppableWallet/UnstoppableWallet/Models/BalancePrimaryValue.swift @@ -1,18 +1,4 @@ enum BalancePrimaryValue: String, CaseIterable, Codable { case coin case currency - - var title: String { - switch self { - case .coin: return "appearance.balance_value.coin_value".localized - case .currency: return "appearance.balance_value.fiat_value".localized - } - } - - var subtitle: String { - switch self { - case .coin: return "appearance.balance_value.fiat_value".localized - case .currency: return "appearance.balance_value.coin_value".localized - } - } } diff --git a/UnstoppableWallet/UnstoppableWallet/Models/PriceChangeMode.swift b/UnstoppableWallet/UnstoppableWallet/Models/PriceChangeMode.swift new file mode 100644 index 0000000000..e45428f426 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Models/PriceChangeMode.swift @@ -0,0 +1,4 @@ +enum PriceChangeMode: String, CaseIterable, Codable { + case hour24 + case midnightUtc +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/ReleaseNotesViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/ReleaseNotesViewController.swift index 063b1a82f6..31f7c2fd1b 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/ReleaseNotesViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/ReleaseNotesViewController.swift @@ -53,7 +53,7 @@ class ReleaseNotesViewController: MarkdownViewController { } twitterButton.addTarget(self, action: #selector(onTwitterTap), for: .touchUpInside) - twitterButton.setImage(UIImage(named: "filled_twitter_24"), for: .normal) + twitterButton.setImage(UIImage(named: "filled_twitter_24")?.withTintColor(.themeJacob), for: .normal) let telegramButton = UIButton() bottomHolder.addSubview(telegramButton) @@ -64,7 +64,7 @@ class ReleaseNotesViewController: MarkdownViewController { } telegramButton.addTarget(self, action: #selector(onTelegramTap), for: .touchUpInside) - telegramButton.setImage(UIImage(named: "filled_telegram_24"), for: .normal) + telegramButton.setImage(UIImage(named: "filled_telegram_24")?.withTintColor(.themeJacob), for: .normal) let followUsLabel = UILabel() bottomHolder.addSubview(followUsLabel) @@ -74,7 +74,7 @@ class ReleaseNotesViewController: MarkdownViewController { } followUsLabel.font = .caption - followUsLabel.textColor = .themeGray + followUsLabel.textColor = .themeJacob followUsLabel.text = "release_notes.follow_us".localized } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Nft/NftService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Nft/NftService.swift index fa024c1ce1..10de187e54 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Nft/NftService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Nft/NftService.swift @@ -1,3 +1,4 @@ +import Combine import Foundation import MarketKit import RxRelay @@ -13,6 +14,7 @@ class NftService { private let coinPriceService: WalletCoinPriceService private let disposeBag = DisposeBag() private var adapterDisposeBag = DisposeBag() + private var cancellables = Set() var mode: Mode = .lastSale { didSet { @@ -52,7 +54,7 @@ class NftService { self.coinPriceService = coinPriceService subscribe(disposeBag, nftAdapterManager.adaptersUpdatedObservable) { [weak self] in self?.handle(adapterMap: $0) } - subscribe(disposeBag, balanceConversionManager.conversionTokenObservable) { [weak self] _ in self?.syncTotalItem() } + balanceConversionManager.$conversionToken.sink { [weak self] _ in self?.syncTotalItem() }.store(in: &cancellables) _handle(adapterMap: nftAdapterManager.adapterMap) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceView.swift index 595e78985b..90afabdfd4 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceView.swift @@ -5,135 +5,147 @@ import ThemeKit struct AppearanceView: View { @StateObject var viewModel = AppearanceViewModel() + @State private var themeSelectorPresented = false + @State private var priceChangeSelectorPresented = false + @State private var launchScreenSelectorPresented = false + @State private var balanceValueSelectorPresented = false + @State private var conversionSelectorPresented = false + var body: some View { ScrollableThemeView { VStack(spacing: .margin24) { + ListSection { + ClickableRow(spacing: .margin8) { + themeSelectorPresented = true + } content: { + Text("appearance.theme".localized).textBody() + Spacer() + Text(title(themeMode: viewModel.themeMode)).textSubhead1() + Image("arrow_small_down_20").themeIcon() + } + .alert( + isPresented: $themeSelectorPresented, + title: "appearance.theme".localized, + viewItems: viewModel.themeModes.map { .init(text: title(themeMode: $0), selected: viewModel.themeMode == $0) }, + onTap: { index in + guard let index else { + return + } + + viewModel.themeMode = viewModel.themeModes[index] + } + ) + } + VStack(spacing: 0) { + ListSectionHeader(text: "appearance.markets_tab".localized) ListSection { - NavigationRow(spacing: .margin8, destination: { - BaseCurrencySettingsModule.view() - }) { - HStack(spacing: .margin16) { - Image("usd_24").themeIcon() - Text("settings.base_currency".localized).textBody() + ListRow { + Toggle(isOn: $viewModel.hideMarkets.animation()) { + Text("appearance.hide_markets".localized).themeBody() } - Spacer() - Text(viewModel.baseCurrency.code).textSubhead1() - Image.disclosureIcon + .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } - NavigationRow(spacing: .margin8, destination: { - LanguageSettingsModule.view() - }) { - HStack(spacing: .margin16) { - Image("globe_24").themeIcon() - Text("settings.language".localized).textBody() - } + ClickableRow(spacing: .margin8) { + priceChangeSelectorPresented = true + } content: { + Text("appearance.price_change".localized).textBody() Spacer() - if let language = viewModel.currentLanguageDisplayName { - Text(language).textSubhead1() - } - Image.disclosureIcon + Text(title(priceChangeMode: viewModel.priceChangeMode)).textSubhead1() + Image("arrow_small_down_20").themeIcon() } + .alert( + isPresented: $priceChangeSelectorPresented, + title: "appearance.price_change".localized, + viewItems: PriceChangeMode.allCases.map { .init(text: title(priceChangeMode: $0), selected: viewModel.priceChangeMode == $0) }, + onTap: { index in + guard let index else { + return + } + + viewModel.priceChangeMode = PriceChangeMode.allCases[index] + } + ) } } - VStack(spacing: 0) { - ListSectionHeader(text: "appearance.theme".localized) + if !viewModel.hideMarkets { ListSection { - ForEach(viewModel.themeModes, id: \.self) { themeMode in - ClickableRow(action: { - viewModel.themMode = themeMode - }) { - icon(themeMode: themeMode).themeIcon() - Text(title(themeMode: themeMode)).themeBody() - - if viewModel.themMode == themeMode { - Image.checkIcon + ClickableRow(spacing: .margin8) { + launchScreenSelectorPresented = true + } content: { + Text("appearance.launch_screen".localized).textBody() + Spacer() + Text(viewModel.launchScreen.title).textSubhead1() + Image("arrow_small_down_20").themeIcon() + } + .alert( + isPresented: $launchScreenSelectorPresented, + title: "appearance.launch_screen".localized, + viewItems: LaunchScreen.allCases.map { .init(text: $0.title, selected: viewModel.launchScreen == $0) }, + onTap: { index in + guard let index else { + return } + + viewModel.launchScreen = LaunchScreen.allCases[index] } - } + ) } } VStack(spacing: 0) { - ListSectionHeader(text: "appearance.tab_settings".localized) + ListSectionHeader(text: "appearance.balance_tab".localized) ListSection { ListRow { - Image("markets_24").themeIcon() - Toggle(isOn: $viewModel.showMarketTab.animation()) { - Text("appearance.markets_tab".localized).themeBody() + Toggle(isOn: $viewModel.hideBalanceButtons.animation()) { + Text("appearance.hide_buttons".localized).themeBody() } .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) } - ListRow { - Image("arrow_swap_24").themeIcon() - Toggle(isOn: $viewModel.showBalanceButtons.animation()) { - Text("appearance.buttons_show".localized).themeBody() - } - .toggleStyle(SwitchToggleStyle(tint: .themeYellow)) - } - } - } - if viewModel.showMarketTab { - VStack(spacing: 0) { - ListSectionHeader(text: "appearance.launch_screen".localized) - ListSection { - ForEach(LaunchScreen.allCases, id: \.self) { launchScreen in - ClickableRow(action: { - viewModel.launchScreen = launchScreen - }) { - Image(launchScreen.iconName).themeIcon() - Text(launchScreen.title).themeBody() - - if viewModel.launchScreen == launchScreen { - Image.checkIcon - } - } - } + ClickableRow(spacing: .margin8) { + balanceValueSelectorPresented = true + } content: { + Text("appearance.balance_value".localized).textBody() + Spacer() + Text(title(balancePrimaryValue: viewModel.balancePrimaryValue)).textSubhead1() + Image("arrow_small_down_20").themeIcon() } - } - } - - VStack(spacing: 0) { - ListSectionHeader(text: "appearance.balance_conversion".localized) - ListSection { - ForEach(viewModel.conversionTokens, id: \.self) { token in - ClickableRow(action: { - viewModel.conversionToken = token - }) { - KFImage.url(URL(string: token.coin.imageUrl)) - .resizable() - .frame(width: .iconSize32, height: .iconSize32) - - Text(token.coin.code).themeBody() - - if viewModel.conversionToken == token { - Image.checkIcon + .alert( + isPresented: $balanceValueSelectorPresented, + title: "appearance.balance_value".localized, + viewItems: BalancePrimaryValue.allCases.map { .init(text: title(balancePrimaryValue: $0), selected: viewModel.balancePrimaryValue == $0) }, + onTap: { index in + guard let index else { + return } + + viewModel.balancePrimaryValue = BalancePrimaryValue.allCases[index] } - } - } - } + ) - VStack(spacing: 0) { - ListSectionHeader(text: "appearance.balance_value".localized) - ListSection { - ForEach(BalancePrimaryValue.allCases, id: \.self) { balancePrimaryValue in - ClickableRow(action: { - viewModel.balancePrimaryValue = balancePrimaryValue - }) { - VStack(spacing: 1) { - Text(balancePrimaryValue.title).themeBody() - Text(balancePrimaryValue.subtitle).themeSubhead2() + ClickableRow(spacing: .margin8) { + conversionSelectorPresented = true + } content: { + Text("appearance.balance_conversion".localized).textBody() + Spacer() + Text(viewModel.conversionToken?.coin.code ?? "").textSubhead1() + Image("arrow_small_down_20").themeIcon() + } + .alert( + isPresented: $conversionSelectorPresented, + title: "appearance.balance_conversion".localized, + viewItems: viewModel.conversionTokens.map { .init(text: $0.coin.code, selected: viewModel.conversionToken == $0) }, + onTap: { index in + guard let index else { + return } - if viewModel.balancePrimaryValue == balancePrimaryValue { - Image.checkIcon - } + viewModel.conversionToken = viewModel.conversionTokens[index] } - } + ) } } @@ -164,6 +176,7 @@ struct AppearanceView: View { .padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16)) } .navigationTitle("appearance.title".localized) + .navigationBarTitleDisplayMode(.inline) } func title(themeMode: ThemeMode) -> String { @@ -174,11 +187,17 @@ struct AppearanceView: View { } } - func icon(themeMode: ThemeMode) -> Image { - switch themeMode { - case .system: return Image("settings_24") - case .dark: return Image("dark_24") - case .light: return Image("light_24") + func title(balancePrimaryValue: BalancePrimaryValue) -> String { + switch balancePrimaryValue { + case .coin: return "appearance.balance_value.coin_fiat".localized + case .currency: return "appearance.balance_value.fiat_coin".localized + } + } + + func title(priceChangeMode: PriceChangeMode) -> String { + switch priceChangeMode { + case .hour24: return "appearance.price_change.24h".localized + case .midnightUtc: return "appearance.price_change.midnight_utc".localized } } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift index 90eccf7e0e..381c7bdf26 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Appearance/AppearanceViewModel.swift @@ -12,33 +12,26 @@ class AppearanceViewModel: ObservableObject { private let balancePrimaryValueManager = App.shared.balancePrimaryValueManager private let balanceConversionManager = App.shared.balanceConversionManager private let walletButtonHiddenManager = App.shared.walletButtonHiddenManager - private let currencyManager = App.shared.currencyManager - private let languageManager = LanguageManager.shared + private let priceChangeModeManager = App.shared.priceChangeModeManager let themeModes: [ThemeMode] = [.system, .dark, .light] let conversionTokens: [Token] - var currentLanguageDisplayName: String? { - languageManager.currentLanguageDisplayName - } - - @Published var baseCurrency: Currency - - @Published var themMode: ThemeMode { + @Published var themeMode: ThemeMode { didSet { - themeManager.themeMode = themMode + themeManager.themeMode = themeMode } } - @Published var showMarketTab: Bool { + @Published var hideMarkets: Bool { didSet { - launchScreenManager.showMarket = showMarketTab + launchScreenManager.showMarket = !hideMarkets } } - @Published var showBalanceButtons: Bool { + @Published var priceChangeMode: PriceChangeMode { didSet { - walletButtonHiddenManager.buttonHidden = !showBalanceButtons + priceChangeModeManager.priceChangeMode = priceChangeMode } } @@ -48,9 +41,9 @@ class AppearanceViewModel: ObservableObject { } } - @Published var conversionToken: Token? { + @Published var hideBalanceButtons: Bool { didSet { - balanceConversionManager.set(conversionToken: conversionToken) + walletButtonHiddenManager.buttonHidden = hideBalanceButtons } } @@ -60,6 +53,12 @@ class AppearanceViewModel: ObservableObject { } } + @Published var conversionToken: Token? { + didSet { + balanceConversionManager.set(conversionToken: conversionToken) + } + } + @Published var appIcon: AppIcon { didSet { appIconManager.appIcon = appIcon @@ -69,15 +68,15 @@ class AppearanceViewModel: ObservableObject { init() { conversionTokens = balanceConversionManager.conversionTokens - themMode = themeManager.themeMode - showMarketTab = launchScreenManager.showMarket + themeMode = themeManager.themeMode + hideMarkets = !launchScreenManager.showMarket + priceChangeMode = priceChangeModeManager.priceChangeMode launchScreen = launchScreenManager.launchScreen - conversionToken = balanceConversionManager.conversionToken + hideBalanceButtons = walletButtonHiddenManager.buttonHidden balancePrimaryValue = balancePrimaryValueManager.balancePrimaryValue + conversionToken = balanceConversionManager.conversionToken appIcon = appIconManager.appIcon - baseCurrency = currencyManager.baseCurrency - showBalanceButtons = !walletButtonHiddenManager.buttonHidden - currencyManager.$baseCurrency.sink { [weak self] in self?.baseCurrency = $0 }.store(in: &cancellables) + balanceConversionManager.$conversionToken.sink { [weak self] in self?.conversionToken = $0 }.store(in: &cancellables) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsModule.swift index cc16254590..967d1edf0d 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsModule.swift @@ -11,6 +11,7 @@ enum MainSettingsModule { passcodeManager: App.shared.passcodeManager, termsManager: App.shared.termsManager, systemInfoManager: App.shared.systemInfoManager, + currencyManager: App.shared.currencyManager, walletConnectSessionManager: App.shared.walletConnectSessionManager, subscriptionManager: App.shared.subscriptionManager, rateAppManager: App.shared.rateAppManager diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsService.swift index 0bef9d114c..f9f8e5c890 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsService.swift @@ -14,6 +14,7 @@ class MainSettingsService { private let passcodeManager: PasscodeManager private let termsManager: TermsManager private let systemInfoManager: SystemInfoManager + private let currencyManager: CurrencyManager private let walletConnectSessionManager: WalletConnectSessionManager private let subscriptionManager: SubscriptionManager private let rateAppManager: RateAppManager @@ -22,7 +23,7 @@ class MainSettingsService { private let noWalletRequiredActionsRelay = BehaviorRelay(value: false) init(backupManager: BackupManager, cloudAccountBackupManager: CloudBackupManager, accountRestoreWarningManager: AccountRestoreWarningManager, accountManager: AccountManager, contactBookManager: ContactBookManager, passcodeManager: PasscodeManager, termsManager: TermsManager, - systemInfoManager: SystemInfoManager, walletConnectSessionManager: WalletConnectSessionManager, subscriptionManager: SubscriptionManager, rateAppManager: RateAppManager) + systemInfoManager: SystemInfoManager, currencyManager: CurrencyManager, walletConnectSessionManager: WalletConnectSessionManager, subscriptionManager: SubscriptionManager, rateAppManager: RateAppManager) { self.cloudAccountBackupManager = cloudAccountBackupManager self.backupManager = backupManager @@ -32,6 +33,7 @@ class MainSettingsService { self.passcodeManager = passcodeManager self.termsManager = termsManager self.systemInfoManager = systemInfoManager + self.currencyManager = currencyManager self.walletConnectSessionManager = walletConnectSessionManager self.subscriptionManager = subscriptionManager self.rateAppManager = rateAppManager @@ -104,6 +106,18 @@ extension MainSettingsService { walletConnectSessionManager.activePendingRequestsObservable.map(\.count) } + var currentLanguageDisplayName: String? { + LanguageManager.shared.currentLanguageDisplayName + } + + var baseCurrency: Currency { + currencyManager.baseCurrency + } + + var baseCurrencyPublisher: AnyPublisher { + currencyManager.$baseCurrency + } + var appVersion: String { systemInfoManager.appVersion.description } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift index 9d7e296fda..47952df84c 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewController.swift @@ -22,6 +22,8 @@ class MainSettingsViewController: ThemeViewController { private let securityCell = BaseSelectableThemeCell() private let appearanceCell = BaseSelectableThemeCell() private let contactBookCell = BaseSelectableThemeCell() + private let baseCurrencyCell = BaseSelectableThemeCell() + private let languageCell = BaseSelectableThemeCell() private let themeModeCell = BaseSelectableThemeCell() private let aboutCell = BaseSelectableThemeCell() private let footerCell = MainSettingsFooterCell() @@ -48,6 +50,7 @@ class MainSettingsViewController: ThemeViewController { super.viewDidLoad() title = "settings.title".localized + navigationItem.largeTitleDisplayMode = .never navigationItem.backBarButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil) tableView.registerHeaderFooter(forClass: HighlightedSubtitleHeaderFooterView.self) @@ -64,18 +67,24 @@ class MainSettingsViewController: ThemeViewController { manageAccountsCell.set(backgroundStyle: .lawrence, isFirst: true) syncManageAccountCell() - walletConnectCell.set(backgroundStyle: .lawrence) + walletConnectCell.set(backgroundStyle: .lawrence, isLast: true) syncWalletConnectCell() securityCell.set(backgroundStyle: .lawrence, isFirst: true) syncSecurityCell() - appearanceCell.set(backgroundStyle: .lawrence, isLast: true) + appearanceCell.set(backgroundStyle: .lawrence) buildTitleValue(cell: appearanceCell, image: UIImage(named: "brush_24"), title: "appearance.title".localized) contactBookCell.set(backgroundStyle: .lawrence) syncContactBookCell() + baseCurrencyCell.set(backgroundStyle: .lawrence) + syncBaseCurrency() + + languageCell.set(backgroundStyle: .lawrence, isLast: true) + buildTitleValue(cell: languageCell, image: UIImage(named: "globe_24"), title: "settings.language".localized, value: viewModel.currentLanguage) + aboutCell.set(backgroundStyle: .lawrence, isFirst: true) syncAboutCell() @@ -93,6 +102,7 @@ class MainSettingsViewController: ThemeViewController { subscribe(disposeBag, viewModel.walletConnectCountDriver) { [weak self] tuple in self?.syncWalletConnectCell(text: tuple?.text, highlighted: tuple?.highlighted ?? false) } + subscribe(disposeBag, viewModel.baseCurrencyDriver) { [weak self] in self?.syncBaseCurrency(value: $0) } subscribe(disposeBag, viewModel.aboutAlertDriver) { [weak self] in self?.syncAboutCell(alert: $0) } subscribe(disposeBag, viewModel.openWalletConnectSignal) { [weak self] in self?.openWalletConnect(mode: $0) } @@ -160,6 +170,10 @@ class MainSettingsViewController: ThemeViewController { ) } + private func syncBaseCurrency(value: String? = nil) { + buildTitleValue(cell: baseCurrencyCell, image: UIImage(named: "usd_24"), title: "settings.base_currency".localized, value: value) + } + private func buildTitleValue(cell: BaseThemeCell, image: UIImage?, title: String, value: String? = nil, badge: String? = nil) { CellBuilderNew.buildStatic(cell: cell, rootElement: .hStack([ .image24 { (component: ImageComponent) in @@ -214,7 +228,6 @@ class MainSettingsViewController: ThemeViewController { image: .local(UIImage(named: "blocks_24")), title: .body("settings.blockchain_settings".localized), accessoryType: .disclosure, - isLast: false, action: { [weak self] in let viewController = BlockchainSettingsModule.view().toViewController(title: "blockchain_settings.title".localized) self?.navigationController?.pushViewController(viewController, animated: true) @@ -231,19 +244,6 @@ class MainSettingsViewController: ThemeViewController { self?.viewModel.onTapWalletConnect() } ), - tableView.universalRow48( - id: "backup-manager", - image: .local(UIImage(named: "icloud_24")), - title: .body("settings.backup_manager".localized), - accessoryType: .disclosure, - isLast: true, - action: { [weak self] in - let viewController = BackupManagerModule.viewController() - self?.navigationController?.pushViewController(viewController, animated: true) - - stat(page: .settings, event: .open(page: .backupManager)) - } - ), ] } @@ -284,6 +284,27 @@ class MainSettingsViewController: ThemeViewController { stat(page: .settings, event: .open(page: .appearance)) } ), + StaticRow( + cell: baseCurrencyCell, + id: "base-currency", + height: .heightCell48, + action: { [weak self] in + self?.navigationController?.pushViewController(BaseCurrencySettingsModule.view().toViewController(title: "settings.base_currency.title".localized), animated: true) + + stat(page: .settings, event: .open(page: .baseCurrency)) + } + ), + StaticRow( + cell: languageCell, + id: "language", + height: .heightCell48, + action: { [weak self] in + let module = LanguageSettingsModule.view().toViewController(title: "settings.language".localized) + self?.navigationController?.pushViewController(module, animated: true) + + stat(page: .settings, event: .open(page: .language)) + } + ), ] } @@ -316,6 +337,25 @@ class MainSettingsViewController: ThemeViewController { ] } + private var backupRows: [RowProtocol] { + [ + tableView.universalRow48( + id: "backup-manager", + image: .local(UIImage(named: "icloud_24")), + title: .body("settings.backup_manager".localized), + accessoryType: .disclosure, + isFirst: true, + isLast: true, + action: { [weak self] in + let viewController = BackupManagerModule.viewController() + self?.navigationController?.pushViewController(viewController, animated: true) + + stat(page: .settings, event: .open(page: .backupManager)) + } + ), + ] + } + private var socialRows: [RowProtocol] { [ tableView.universalRow48( @@ -502,6 +542,7 @@ extension MainSettingsViewController: SectionsDataSource { rows: socialRows ), Section(id: "knowledge", headerState: .margin(height: .margin32), rows: knowledgeRows), + Section(id: "backup", headerState: .margin(height: .margin32), rows: backupRows), Section(id: "about", headerState: .margin(height: .margin32), rows: aboutRows), Section(id: "footer", headerState: .margin(height: .margin32), footerState: .margin(height: .margin32), rows: footerRows), ] diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewModel.swift index 109ec14dfc..c9e8cc47f5 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/Main/MainSettingsViewModel.swift @@ -13,6 +13,7 @@ class MainSettingsViewModel { private let securityCenterAlertRelay: BehaviorRelay private let iCloudSyncAlertRelay: BehaviorRelay private let walletConnectCountRelay: BehaviorRelay<(highlighted: Bool, text: String)?> + private let baseCurrencyRelay: BehaviorRelay private let aboutAlertRelay: BehaviorRelay private let openWalletConnectRelay = PublishRelay() private let openLinkRelay = PublishRelay() @@ -24,6 +25,7 @@ class MainSettingsViewModel { securityCenterAlertRelay = BehaviorRelay(value: !service.isPasscodeSet) iCloudSyncAlertRelay = BehaviorRelay(value: service.isCloudAvailableError) walletConnectCountRelay = BehaviorRelay(value: Self.convert(walletConnectSessionCount: service.walletConnectSessionCount, walletConnectPendingRequestCount: service.walletConnectPendingRequestCount)) + baseCurrencyRelay = BehaviorRelay(value: service.baseCurrency.code) aboutAlertRelay = BehaviorRelay(value: !service.termsAccepted) service.noWalletRequiredActionsObservable @@ -60,6 +62,12 @@ class MainSettingsViewModel { }) .disposed(by: disposeBag) + service.baseCurrencyPublisher + .sink { [weak self] currency in + self?.baseCurrencyRelay.accept(currency.code) + } + .store(in: &cancellables) + service.termsAcceptedPublisher .sink { [weak self] accepted in self?.aboutAlertRelay.accept(!accepted) @@ -100,10 +108,18 @@ extension MainSettingsViewModel { walletConnectCountRelay.asDriver() } + var baseCurrencyDriver: Driver { + baseCurrencyRelay.asDriver() + } + var aboutAlertDriver: Driver { aboutAlertRelay.asDriver() } + var currentLanguage: String? { + service.currentLanguageDisplayName + } + var appVersion: String { service.appVersion } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift index 62ed3635c9..0563aa8784 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Wallet/WalletService.swift @@ -42,6 +42,7 @@ class WalletService { private let userDefaultsStorage: UserDefaultsStorage private let sorter = WalletSorter() private let disposeBag = DisposeBag() + private var cancellables = Set() private var internalState: State = .loading { didSet { @@ -122,9 +123,8 @@ class WalletService { subscribe(disposeBag, appManager.willEnterForegroundObservable) { [weak self] in self?.coinPriceService.refresh() } - subscribe(disposeBag, balanceConversionManager.conversionTokenObservable) { [weak self] _ in - self?.syncTotalItem() - } + + balanceConversionManager.$conversionToken.sink { [weak self] _ in self?.syncTotalItem() }.store(in: &cancellables) sync(activeAccount: accountManager.activeAccount) } diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index 72f2cde95a..77edb7d63b 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -1536,22 +1536,26 @@ "appearance.theme.dark" = "Dark"; "appearance.theme.light" = "Light"; -"appearance.tab_settings" = "Tab Settings"; "appearance.markets_tab" = "Markets Tab"; -"appearance.buttons_show" = "Balance Tab Buttons"; +"appearance.hide_markets" = "Hide Markets"; +"appearance.price_change" = "Price Change"; +"appearance.price_change.24h" = "24H"; +"appearance.price_change.midnight_utc" = "Midnight UTC"; + "appearance.launch_screen" = "Launch Screen"; "appearance.launch_screen.auto" = "Auto"; "appearance.launch_screen.balance" = "Balance"; "appearance.launch_screen.market_overview" = "Market Overview"; "appearance.launch_screen.watchlist" = "Watchlist"; -"appearance.app_icon" = "App Icon"; - +"appearance.balance_tab" = "Balance Tab"; +"appearance.hide_buttons" = "Hide Buttons"; +"appearance.balance_value" = "Balance Value"; +"appearance.balance_value.coin_fiat" = "Coin / Fiat"; +"appearance.balance_value.fiat_coin" = "Fiat / Coin"; "appearance.balance_conversion" = "Balance Conversion"; -"appearance.balance_value" = "Balance Value"; -"appearance.balance_value.coin_value" = "Coin Value"; -"appearance.balance_value.fiat_value" = "Fiat Value"; +"appearance.app_icon" = "App Icon"; // Settings -> Contacts