From 1f8e72b2e6e22bda63a78d5b63dc6133081668c5 Mon Sep 17 00:00:00 2001 From: EA Date: Tue, 21 May 2024 18:40:49 +0600 Subject: [PATCH] Implement watchlist signals approve view --- .../project.pbxproj | 12 ++ .../Icons/check_2_24.imageset/check-2@2x.png | Bin 420 -> 399 bytes .../Icons/check_2_24.imageset/check-2@3x.png | Bin 540 -> 511 bytes .../MarketAdvancedSearchViewModel.swift | 2 +- .../MarketWatchlistSignalBadge.swift | 37 ++++++ .../MarketWatchlistSignalsView.swift | 121 ++++++++++++++++++ .../Watchlist/MarketWatchlistView.swift | 43 ++----- .../Watchlist/MarketWatchlistViewModel.swift | 10 ++ .../BlockchainSettingsView.swift | 2 +- .../en.lproj/Localizable.strings | 12 +- 10 files changed, 206 insertions(+), 33 deletions(-) create mode 100644 UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistSignalBadge.swift create mode 100644 UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistSignalsView.swift diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 4329a43bd3..31540e6271 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -2976,6 +2976,10 @@ D31C4761238BF176008CB818 /* MnemonicDerivation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31C4759238BF175008CB818 /* MnemonicDerivation.swift */; }; D31C4763238BF176008CB818 /* FeeRateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31C475A238BF175008CB818 /* FeeRateState.swift */; }; D31C4764238BF176008CB818 /* FeeRateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31C475A238BF175008CB818 /* FeeRateState.swift */; }; + D3384D092BFCB43800515664 /* MarketWatchlistSignalsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D082BFCB43800515664 /* MarketWatchlistSignalsView.swift */; }; + D3384D0A2BFCB43800515664 /* MarketWatchlistSignalsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D082BFCB43800515664 /* MarketWatchlistSignalsView.swift */; }; + D3384D0C2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D0B2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift */; }; + D3384D0D2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3384D0B2BFCB8F000515664 /* MarketWatchlistSignalBadge.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 */; }; @@ -4901,6 +4905,8 @@ D3285F5120BD158F00644076 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D3373D9420BEC7B30082BC4A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; D3373DB120C52F640082BC4A /* LaunchScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; + D3384D082BFCB43800515664 /* MarketWatchlistSignalsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWatchlistSignalsView.swift; sourceTree = ""; }; + D3384D0B2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWatchlistSignalBadge.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 = ""; }; @@ -9329,6 +9335,8 @@ D3402AED2BF5D58B003BF6F8 /* WatchlistViewModel.swift */, D3833AD62BEE1A7900ACECFB /* MarketWatchlistView.swift */, D3833AD92BEE1A8300ACECFB /* MarketWatchlistViewModel.swift */, + D3384D082BFCB43800515664 /* MarketWatchlistSignalsView.swift */, + D3384D0B2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift */, ); path = Watchlist; sourceTree = ""; @@ -9851,6 +9859,7 @@ 11B358B0576F63BE43947DD5 /* Account.swift in Sources */, 11B35BEB439509EACB41AB06 /* AccountType.swift in Sources */, 11B35F663F7E12BFDDE3C88B /* AccountManager.swift in Sources */, + D3384D0D2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift in Sources */, 11B353AA4AFFB020A68E09B6 /* AccountFactory.swift in Sources */, 3C7B9BAF807355796DCA80C4 /* WelcomeScreenViewController.swift in Sources */, 11B35BCD6D0462E31D7EBA06 /* BackupManager.swift in Sources */, @@ -11205,6 +11214,7 @@ 11B3523804E0F4F1DA8A1D9E /* BaseCurrencySettingsViewModel.swift in Sources */, 11B35F18FEEEAA9EC6043CA6 /* BaseCurrencySettingsView.swift in Sources */, 11B35E749106C1ABD9335778 /* TransactionFilterModule.swift in Sources */, + D3384D0A2BFCB43800515664 /* MarketWatchlistSignalsView.swift in Sources */, 11B3591E867F4E701F5458F9 /* TransactionFilterView.swift in Sources */, 11B352E8348A715EB537F643 /* TransactionFilterViewModel.swift in Sources */, 11B35AAD64D68265B2128C25 /* TransactionBlockchainSelectView.swift in Sources */, @@ -11414,6 +11424,7 @@ 11B35068E05BC58C6C9A93D7 /* AccountManager.swift in Sources */, 11B35C8621E221DA1F157A5B /* AccountFactory.swift in Sources */, 3C7B9F51D15FBB02710E5EEB /* WelcomeScreenViewController.swift in Sources */, + D3384D0C2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift in Sources */, 11B35BC6DFCA197FA842873B /* BackupManager.swift in Sources */, 11B35590A4DA4BCFB3D38DDF /* AppManager.swift in Sources */, D31C4761238BF176008CB818 /* MnemonicDerivation.swift in Sources */, @@ -12768,6 +12779,7 @@ ABC9AE558CE5912B5C15B6EB /* ActionSheetControllerNew.swift in Sources */, 11B3537EE13B3EFB2E979821 /* BadgeViewNew.swift in Sources */, 11B35955EE2F47EFAFCBCE9F /* BaseCurrencySettingsViewModel.swift in Sources */, + D3384D092BFCB43800515664 /* MarketWatchlistSignalsView.swift in Sources */, 11B3565D4E4EAD663143ED9B /* BaseCurrencySettingsView.swift in Sources */, 11B353BAEF83867422611E7B /* TransactionFilterModule.swift in Sources */, 11B35B09AADB1FBF7DDE765C /* TransactionFilterView.swift in Sources */, diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/check_2_24.imageset/check-2@2x.png b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/check_2_24.imageset/check-2@2x.png index 991b559865fd85fbb43252df541d55b9a7b4194e..0844165b52df7db98b6d0c26175b7633522c25fa 100644 GIT binary patch delta 322 zcmV-I0logD1CIlcR(~W(L_t(|0qxbnZNe}Z1>oQ90*ufRIs&L2fDPInd+fD6U=HoM z8xRHoWdzs&HedtehawSz67m~tb9kSGa2x{fN#TzO5fKp)5d|`G2d2}%H|N~HU+#=C zb~5>%_r3N(GONNDemU^3r>aPASqs#{*X=|hTY;AFhf|`E<$piW5&py4pF$Lp7r143 zNLt|5;UQUp7~w%xAaq{G3Jwi)wD1sI0D6>&8Xm4H039Wwhli^Rz_EmRuC_i?geM{*A|lFb4=EJ7 Uf8tojIsgCw07*qoM6N<$f^e*gg#Z8m delta 343 zcmV-d0jU0u1Ed3xR)0B3L_t(|0qxbzZNe}B1z@{ffDt-EM*y`0utD2nk6ht^Il!3> z2!nt!0&D;qutD%ck+1@BoFChfc%OtYc1+%r0v3XZh=_^0itaDDjz3{f7uoL_|dN>aOD8+=;Cg#FPL4002ovPDHLkV1hExq&)xt diff --git a/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/check_2_24.imageset/check-2@3x.png b/UnstoppableWallet/UnstoppableWallet/Assets.xcassets/Icons/check_2_24.imageset/check-2@3x.png index ca3f994bd76b3f6870fa05e1eb251855da2fb89c..c24f8f6f45a0070de666e565f00195f45a000100 100644 GIT binary patch delta 435 zcmbQk@}GG^Sp5`F7srqa#X?-|5fKm29U#eHZ0!FI?B?o4;m#8{)XmN-N zNHI*GAf(YeHL>rJtJp_vG9HTbMLFS zQA{&@6s|ruFF#RU7q#+UZ1-|cmRrKhgY-h>x#~X}Oh0nBFS!QLy& z-o(C+f6o7I&f4GVx|`lSTB5PfZuYulL%s@jrt;6(^F6{IJ4|pW$WS?5s1Tv> zLi%KtOvk24VYN{^z2i;iY+BV<`B(Gt@y3kkUcQM^H#(Rdm)}^&cX@5wTnhnRfiL|j z+I%j&EO(V}b~9gOcC1#JU6?SzAwd3Qo{WT!K#sC*f9w+D?>b+vzkM8cclLQ{MI{8t a`poYm{Bp^>!zHU2fWXt$&t;ucLK6V{vba0Wbdl1DphqR)4WcL_t(|0qxp7PQx%5#__jhVdVy}F?M83;UI()P*y&K2S8#% zoPhQQZ~@4%m|Fa(5ezhGT*uC9>-kAllqQOlzm!O}BSJ((L_|bHL_|bHL@JCVW5zt) zi>zkrz0us5>nm}zCz&$l@y%`!XTScP6vcE@RbQ3EWn?TFs(<5@|Bs9>#Zh*Fn&a#y zt3cc1EEnP^o0s(VIID#*x1~7BB2cI}kbIz!aUkhHq2oZZfnvmgBm>2Y1IY!783&RI z6gv(i6KIGykVK%N;(&LcKBg4fuRmNI@Csz(y!^%aWh32ONDqdN1D=D`Rk`8Mvc54d z8sb2x(Y#kAEq`&qGtlz2<={?{G{ph0DOXHi7RBk2eU(ibPMH+N*kz4hjyMo(WwpcO)<91j2)gv_9!Xam2);JoJxP+jI1uIIh3%1a#(`*GcQi-R z8wa9({<4lVJ+P6E?e4c7>^>WFHxnWvA|fIpA|fK9Ec^h7_y_WWOW2110000 some View { + ListRow { + MarketWatchlistSignalBadge(signal: signal) + .background( + GeometryReader { geometry in + Color.clear.preference(key: MaxWidthPreferenceKey.self, value: geometry.size.width) + } + .scaledToFill() + ) + .frame(width: maxBadgeWidth) + + Text(description(signal: signal)).themeSubhead2(color: .themeLeah) + } + } + + private func description(signal: TechnicalAdvice.Advice) -> String { + switch signal { + case .neutral: return "market.watchlist.signals.neutral.description".localized + case .buy: return "market.watchlist.signals.buy.description".localized + case .sell: return "market.watchlist.signals.sell.description".localized + case .strongBuy: return "market.watchlist.signals.strong_buy.description".localized + case .strongSell: return "market.watchlist.signals.strong_sell.description".localized + case .overbought, .oversold: return "market.watchlist.signals.risky.description".localized + } + } + + private struct MaxWidthPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = .zero + + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + let nextValue = nextValue() + guard nextValue > value else { return } + value = nextValue + } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift index b24b41cf9c..1440e11852 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistView.swift @@ -8,6 +8,7 @@ struct MarketWatchlistView: View { @State private var sortBySelectorPresented = false @State private var timePeriodSelectorPresented = false @State private var presentedFullCoin: FullCoin? + @State private var signalsPresented = false @State private var editMode: EditMode = .inactive @@ -111,11 +112,20 @@ struct MarketWatchlistView: View { viewModel.timePeriod = WatchlistTimePeriod.allCases[index] } ) + .sheet(isPresented: $signalsPresented) { + MarketWatchlistSignalsView(viewModel: viewModel, isPresented: $signalsPresented) + } } @ViewBuilder private func signalsButton() -> some View { Button(action: { - viewModel.showSignals.toggle() + if viewModel.showSignals { + viewModel.showSignals = false + } else if viewModel.signalsApproved { + viewModel.showSignals = true + } else { + signalsPresented = true + } }) { Text("market.watchlist.signals".localized) } @@ -191,17 +201,11 @@ struct MarketWatchlistView: View { VStack(spacing: 1) { HStack(spacing: .margin8) { - HStack(spacing: .margin12) { + HStack(spacing: .margin8) { Text(code).textBody() if let signal { - Text(signal.searchTitle) - .font(.themeMicroSB) - .foregroundColor(foregroundColor(signal: signal)) - .padding(.horizontal, .margin6) - .padding(.vertical, .margin2) - .background(RoundedRectangle(cornerRadius: .cornerRadius8, style: .continuous).fill(backgroundColor(signal: signal))) - .clipShape(RoundedRectangle(cornerRadius: .cornerRadius8, style: .continuous)) + MarketWatchlistSignalBadge(signal: signal) } } @@ -224,25 +228,4 @@ struct MarketWatchlistView: View { } } } - - private func foregroundColor(signal: TechnicalAdvice.Advice) -> Color { - switch signal { - case .neutral: return .themeBran - case .buy: return .themeRemus - case .sell: return .themeLucian - case .strongBuy, .strongSell: return .themeTyler - case .overbought, .oversold: return .themeJacob - } - } - - private func backgroundColor(signal: TechnicalAdvice.Advice) -> Color { - switch signal { - case .neutral: return .themeSteel20 - case .buy: return .themeGreen.opacity(0.2) - case .sell: return .themeRed.opacity(0.2) - case .strongBuy: return .themeRemus - case .strongSell: return .themeLucian - case .overbought, .oversold: return .themeYellow20 - } - } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift index 510d97b0fb..23cad81d82 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Market/Watchlist/MarketWatchlistViewModel.swift @@ -4,9 +4,12 @@ import HsExtensions import MarketKit class MarketWatchlistViewModel: ObservableObject { + private let keySignalsApproved = "market-watchlist-signals-approved" + private let marketKit = App.shared.marketKit private let currencyManager = App.shared.currencyManager private let watchlistManager = App.shared.watchlistManager + private let userDefaultsStorage = App.shared.userDefaultsStorage private var cancellables = Set() private var tasks = Set() @@ -42,10 +45,17 @@ class MarketWatchlistViewModel: ObservableObject { } } + var signalsApproved: Bool { + didSet { + userDefaultsStorage.set(value: true, for: keySignalsApproved) + } + } + init() { sortBy = watchlistManager.sortBy timePeriod = watchlistManager.timePeriod showSignals = watchlistManager.showSignals + signalsApproved = userDefaultsStorage.value(for: keySignalsApproved) ?? false } private func syncCoinUids() { diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BlockchainSettings/BlockchainSettingsView.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BlockchainSettings/BlockchainSettingsView.swift index 4f1ce48adc..bd9c51dc85 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BlockchainSettings/BlockchainSettingsView.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Settings/BlockchainSettings/BlockchainSettingsView.swift @@ -41,7 +41,7 @@ struct BlockchainSettingsView: View { } } .sheet(item: $evmSheetBlockchain) { blockchain in - EvmNetworkView(blockchain: blockchain) + EvmNetworkView(blockchain: blockchain).ignoresSafeArea() } } } diff --git a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings index 979b719f66..f00da80854 100644 --- a/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings @@ -797,6 +797,16 @@ "market_watchlist.empty.caption" = "Your watchlist is empty."; "market.watchlist.signals" = "Signals"; "market.watchlist.empty" = "Your watchlist is empty"; +"market.watchlist.signals.description" = "Signals are based on the Bollinger Bands + RSI strategy to determine trading signals. All calculations are candlesticks and provide based on daily advice for a moderately long term."; +"market.watchlist.signals.strong_buy.description" = "High confidence in asset price growth."; +"market.watchlist.signals.buy.description" = "Indicates likely price increase in near future."; +"market.watchlist.signals.neutral.description" = "No clear trend, market is in equilibrium."; +"market.watchlist.signals.sell.description" = "Likely price decrease, considers current market conditions."; +"market.watchlist.signals.strong_sell.description" = "High probability of price decrease."; +"market.watchlist.signals.risky.description" = "Elevated risk level, requires cautious approach."; +"market.watchlist.signals.warning" = "Always remember to apply risk management, and note that this is not financial advice."; +"market.watchlist.signals.dont_show_again" = "Don't show it again"; +"market.watchlist.signals.turn_on" = "Turn On"; "market.advanced_search.title" = "Filters"; "market.advanced_search.show_results" = "Show Results"; @@ -824,7 +834,7 @@ "market.advanced_search.price_period" = "Price Period"; "market.advanced_search.price_change" = "Price Change"; -"market.advanced_search.technical_advice.risk_trade" = "Risk To Trade"; +"market.advanced_search.technical_advice.risky" = "Risky"; "market.advanced_search.technical_advice.strong_buy" = "Strong Buy"; "market.advanced_search.technical_advice.buy" = "Buy"; "market.advanced_search.technical_advice.neutral" = "Neutral";