Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chart view #5306

Merged
merged 2 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

389 changes: 11 additions & 378 deletions UnstoppableWallet/UnstoppableWallet/Modules/Chart/ChartCell.swift

Large diffs are not rendered by default.

407 changes: 407 additions & 0 deletions UnstoppableWallet/UnstoppableWallet/Modules/Chart/ChartUiView.swift

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions UnstoppableWallet/UnstoppableWallet/Modules/Chart/ChartView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Chart
import SwiftUI
import UIKit

struct ChartView: UIViewRepresentable {
typealias UIViewType = UIView

let viewModel: IChartViewModel & IChartViewTouchDelegate
let configuration: ChartConfiguration

func makeUIView(context _: Context) -> UIView {
let chartView = ChartUiView(viewModel: viewModel, configuration: configuration)
chartView.setContentHuggingPriority(.required, for: .vertical)
chartView.onLoad()
return chartView
}

func updateUIView(_: UIView, context _: Context) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import MarketKit
import Chart
import CurrencyKit
import HUD
import Combine

class CoinChartViewModel {
class CoinChartViewModel: ObservableObject {
private let service: CoinChartService
private let factory: CoinChartFactory
private let disposeBag = DisposeBag()
Expand All @@ -24,6 +25,8 @@ class CoinChartViewModel {
private let indicatorsShownRelay = BehaviorRelay<Bool>(value: true)
private let openSettingsRelay = PublishRelay<()>()

@Published private(set) var indicatorsShown: Bool

var intervals: [String] {
service.validIntervals.map { $0.title } + ["chart.time_duration.all".localized]
}
Expand All @@ -32,6 +35,8 @@ class CoinChartViewModel {
self.service = service
self.factory = factory

indicatorsShown = service.indicatorsShown

subscribe(scheduler, disposeBag, service.intervalsUpdatedObservable) { [weak self] in self?.syncIntervalsUpdate() }
subscribe(scheduler, disposeBag, service.periodTypeObservable) { [weak self] in self?.sync(periodType: $0) }
subscribe(scheduler, disposeBag, service.stateObservable) { [weak self] in self?.sync(state: $0) }
Expand All @@ -48,6 +53,7 @@ class CoinChartViewModel {

private func updateIndicatorsShown() {
indicatorsShownRelay.accept(service.indicatorsShown)
indicatorsShown = service.indicatorsShown
}

private func index(periodType: HsPeriodType) -> Int {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,62 @@
import MarketKit
import LanguageKit
import Chart
import LanguageKit
import MarketKit
import SwiftUI

struct CoinOverviewModule {
static func view(coinUid: String) -> some View {
let repository = ChartIndicatorsRepository(
localStorage: App.shared.localStorage,
subscriptionManager: App.shared.subscriptionManager
)
let chartService = CoinChartService(
marketKit: App.shared.marketKit,
currencyKit: App.shared.currencyKit,
localStorage: App.shared.localStorage,
indicatorRepository: repository,
coinUid: coinUid
)
let chartFactory = CoinChartFactory(currentLocale: LanguageManager.shared.currentLocale)
let chartViewModel = CoinChartViewModel(service: chartService, factory: chartFactory)

let viewModel = CoinOverviewViewModelNew(
coinUid: coinUid,
marketKit: App.shared.marketKit,
currencyKit: App.shared.currencyKit,
languageManager: LanguageManager.shared,
accountManager: App.shared.accountManager,
walletManager: App.shared.walletManager
)

return CoinOverviewView(
viewModel: viewModel,
chartViewModel: chartViewModel,
chartIndicatorRepository: repository,
chartPointFetcher: chartService
)
}

static func viewController(coinUid: String) -> CoinOverviewViewController {
let service = CoinOverviewService(
coinUid: coinUid,
marketKit: App.shared.marketKit,
currencyKit: App.shared.currencyKit,
languageManager: LanguageManager.shared,
accountManager: App.shared.accountManager,
walletManager: App.shared.walletManager
coinUid: coinUid,
marketKit: App.shared.marketKit,
currencyKit: App.shared.currencyKit,
languageManager: LanguageManager.shared,
accountManager: App.shared.accountManager,
walletManager: App.shared.walletManager
)

let repository = ChartIndicatorsRepository(
localStorage: App.shared.localStorage,
subscriptionManager: App.shared.subscriptionManager
localStorage: App.shared.localStorage,
subscriptionManager: App.shared.subscriptionManager
)

let chartService = CoinChartService(
marketKit: App.shared.marketKit,
currencyKit: App.shared.currencyKit,
localStorage: App.shared.localStorage,
indicatorRepository: repository,
coinUid: coinUid
marketKit: App.shared.marketKit,
currencyKit: App.shared.currencyKit,
localStorage: App.shared.localStorage,
indicatorRepository: repository,
coinUid: coinUid
)
let router = ChartIndicatorRouter(repository: repository, fetcher: chartService)

Expand All @@ -34,12 +66,11 @@ struct CoinOverviewModule {
let chartViewModel = CoinChartViewModel(service: chartService, factory: chartFactory)

return CoinOverviewViewController(
viewModel: viewModel,
chartViewModel: chartViewModel,
chartRouter: router,
markdownParser: CoinPageMarkdownParser(),
urlManager: UrlManager(inApp: true)
viewModel: viewModel,
chartViewModel: chartViewModel,
chartRouter: router,
markdownParser: CoinPageMarkdownParser(),
urlManager: UrlManager(inApp: true)
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import CurrencyKit
import SDWebImageSwiftUI
import SwiftUI

struct CoinOverviewView: View {
@ObservedObject var viewModel: CoinOverviewViewModelNew
@ObservedObject var chartViewModel: CoinChartViewModel
let chartIndicatorRepository: IChartIndicatorsRepository
let chartPointFetcher: IChartPointFetcher

@State private var chartIndicatorsShown = false

var body: some View {
ThemeView {
ZStack {
switch viewModel.state {
case .loading:
ProgressView()
case let .failed(error):
Text(error.localizedDescription)
case let .completed(item):
let info = item.info
let coin = item.info.fullCoin.coin
let coinCode = coin.code
let rank = info.marketCapRank.map { "#\($0)" }

ScrollView {
VStack(spacing: 0) {
HStack(spacing: .margin16) {
WebImage(url: URL(string: coin.imageUrl))
.placeholder(Image("placeholder_circle_32"))
.resizable()
.scaledToFit()
.frame(width: .iconSize32, height: .iconSize32)

Text(coin.name).themeBody()

if let rank {
Text(rank).themeSubhead1(alignment: .trailing)
}
}
.padding(.horizontal, .margin16)
.padding(.vertical, .margin12)

ChartView(viewModel: chartViewModel, configuration: .coinChart)
.frame(maxWidth: .infinity)
.onAppear {
chartViewModel.start()
}

VStack {
ListSection {
ListRow {
Text("coin_overview.indicators".localized).themeSubhead2()

Button(action: {
chartViewModel.onToggleIndicators()
}) {
Text(chartViewModel.indicatorsShown ? "coin_overview.indicators.hide".localized : "coin_overview.indicators.show".localized)
.animation(.none)
}
.buttonStyle(SecondaryButtonStyle(style: .default))

Button(action: {
chartIndicatorsShown = true
}) {
Image("setting_20").renderingMode(.template)
}
.buttonStyle(SecondaryCircleButtonStyle(style: .default))
}
}

let infoItems = [
format(value: info.marketCap, currency: viewModel.currency).map {
(title: "coin_overview.market_cap".localized, badge: rank, text: $0)
},
format(value: info.totalSupply, coinCode: coinCode).map {
(title: "coin_overview.total_supply".localized, badge: nil, text: $0)
},
format(value: info.circulatingSupply, coinCode: coinCode).map {
(title: "coin_overview.circulating_supply".localized, badge: nil, text: $0)
},
format(value: info.volume24h, currency: viewModel.currency).map {
(title: "coin_overview.trading_volume".localized, badge: nil, text: $0)
},
format(value: info.dilutedMarketCap, currency: viewModel.currency).map {
(title: "coin_overview.diluted_market_cap".localized, badge: nil, text: $0)
},
info.genesisDate.map {
(title: "coin_overview.genesis_date".localized, badge: nil, text: DateHelper.instance.formatFullDateOnly(from: $0))
},
].compactMap { $0 }

if !infoItems.isEmpty {
ListSection {
ForEach(infoItems, id: \.title) { infoItem in
ListRow {
Text(infoItem.title).themeSubhead2()
Text(infoItem.text).themeSubhead1(color: .themeLeah, alignment: .trailing)
}
}
}
}
}
.padding(EdgeInsets(top: 0, leading: .margin16, bottom: 0, trailing: .margin16))
}
}
}
}
}
.onAppear {
viewModel.sync()
}
.sheet(isPresented: $chartIndicatorsShown) {
ChartIndicatorsModule.view(repository: chartIndicatorRepository, fetcher: chartPointFetcher)
.ignoresSafeArea()
}
}

private func format(value: Decimal?, coinCode: String) -> String? {
guard let value = value, !value.isZero else {
return nil
}

return ValueFormatter.instance.formatShort(value: value, decimalCount: 0, symbol: coinCode)
}

private func format(value: Decimal?, currency: Currency) -> String? {
guard let value = value, !value.isZero else {
return nil
}

return ValueFormatter.instance.formatShort(currency: currency, value: value)
}
}
Loading