Skip to content

Commit

Permalink
Implement watchlist signals approve view
Browse files Browse the repository at this point in the history
  • Loading branch information
ealymbaev committed May 22, 2024
1 parent 7ca0afc commit 1f8e72b
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 33 deletions.
12 changes: 12 additions & 0 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -4901,6 +4905,8 @@
D3285F5120BD158F00644076 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
D3384D082BFCB43800515664 /* MarketWatchlistSignalsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWatchlistSignalsView.swift; sourceTree = "<group>"; };
D3384D0B2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWatchlistSignalBadge.swift; sourceTree = "<group>"; };
D3402AED2BF5D58B003BF6F8 /* WatchlistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistViewModel.swift; sourceTree = "<group>"; };
D3402AF02BF5D59D003BF6F8 /* WatchlistModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistModifier.swift; sourceTree = "<group>"; };
D3402AF62BF71C11003BF6F8 /* WatchlistManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchlistManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -9329,6 +9335,8 @@
D3402AED2BF5D58B003BF6F8 /* WatchlistViewModel.swift */,
D3833AD62BEE1A7900ACECFB /* MarketWatchlistView.swift */,
D3833AD92BEE1A8300ACECFB /* MarketWatchlistViewModel.swift */,
D3384D082BFCB43800515664 /* MarketWatchlistSignalsView.swift */,
D3384D0B2BFCB8F000515664 /* MarketWatchlistSignalBadge.swift */,
);
path = Watchlist;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ extension TechnicalAdvice.Advice {

var searchTitle: String {
switch self {
case .oversold, .overbought: return "market.advanced_search.technical_advice.risk_trade".localized
case .oversold, .overbought: return "market.advanced_search.technical_advice.risky".localized
case .strongBuy: return "market.advanced_search.technical_advice.strong_buy".localized
case .buy: return "market.advanced_search.technical_advice.buy".localized
case .neutral: return "market.advanced_search.technical_advice.neutral".localized
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import MarketKit
import SwiftUI

struct MarketWatchlistSignalBadge: View {
let signal: TechnicalAdvice.Advice

var body: some View {
Text(signal.searchTitle)
.font(.themeMicroSB)
.foregroundColor(foregroundColor)
.padding(.horizontal, .margin6)
.padding(.vertical, .margin2)
.background(RoundedRectangle(cornerRadius: .cornerRadius8, style: .continuous).fill(backgroundColor))
.clipShape(RoundedRectangle(cornerRadius: .cornerRadius8, style: .continuous))
}

private var foregroundColor: 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 var backgroundColor: 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
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import MarketKit
import SwiftUI

struct MarketWatchlistSignalsView: View {
@ObservedObject var viewModel: MarketWatchlistViewModel
@Binding var isPresented: Bool

@State private var maxBadgeWidth: CGFloat = .zero
@State private var doNotShowAgain = false

var body: some View {
ThemeNavigationView {
ThemeView {
BottomGradientWrapper {
ScrollView {
VStack(spacing: .margin16) {
Text("market.watchlist.signals.description".localized)
.themeSubhead2()
.padding(EdgeInsets(top: 0, leading: .margin16, bottom: .margin8, trailing: .margin16))

ListSection {
row(signal: .strongBuy)
row(signal: .buy)
row(signal: .neutral)
row(signal: .sell)
row(signal: .strongSell)
row(signal: .overbought)
}
.themeListStyle(.bordered)
.onPreferenceChange(MaxWidthPreferenceKey.self) {
maxBadgeWidth = $0
}

HighlightedTextView(text: "market.watchlist.signals.warning".localized, style: .warning)

ListSection {
ClickableRow(action: {
doNotShowAgain.toggle()
}) {
if doNotShowAgain {
ZStack {
Circle().fill(Color.themeJacob)
Image("check_2_24").themeIcon(color: .themeDark)
}
.frame(width: .iconSize24, height: .iconSize24)
} else {
Circle()
.fill(Color.themeSteel20)
.frame(width: .iconSize24, height: .iconSize24)
}

Text("market.watchlist.signals.dont_show_again".localized).themeSubhead2(color: .themeLeah)
}
}
.themeListStyle(.lawrence)
}
.padding(EdgeInsets(top: .margin12, leading: .margin16, bottom: .margin32, trailing: .margin16))
}
} bottomContent: {
Button(action: {
viewModel.showSignals = true

if doNotShowAgain {
viewModel.signalsApproved = true
}

isPresented = false
}) {
Text("market.watchlist.signals.turn_on".localized)
}
.buttonStyle(PrimaryButtonStyle(style: .yellow))
}
}
.navigationTitle("market.watchlist.signals".localized)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("button.cancel".localized) {
isPresented = false
}
}
}
}
}

@ViewBuilder private func row(signal: TechnicalAdvice.Advice) -> 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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<AnyCancellable>()
private var tasks = Set<AnyTask>()
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct BlockchainSettingsView: View {
}
}
.sheet(item: $evmSheetBlockchain) { blockchain in
EvmNetworkView(blockchain: blockchain)
EvmNetworkView(blockchain: blockchain).ignoresSafeArea()
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion UnstoppableWallet/UnstoppableWallet/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down

0 comments on commit 1f8e72b

Please sign in to comment.