Skip to content

Commit

Permalink
Make coinRank module on swiftUI
Browse files Browse the repository at this point in the history
  • Loading branch information
ant013 committed Jun 4, 2024
1 parent 5a00bb9 commit 7b97554
Show file tree
Hide file tree
Showing 8 changed files with 479 additions and 1 deletion.
20 changes: 20 additions & 0 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,10 @@
6B5F5E0F2C0C65F700E03EB2 /* MarketPlatformViewNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5F5E0D2C0C65F700E03EB2 /* MarketPlatformViewNew.swift */; };
6B5F5E112C0C660900E03EB2 /* MarketPlatformViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5F5E102C0C660900E03EB2 /* MarketPlatformViewModel.swift */; };
6B5F5E122C0C660900E03EB2 /* MarketPlatformViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5F5E102C0C660900E03EB2 /* MarketPlatformViewModel.swift */; };
6B5F5E152C0DDD7100E03EB2 /* RankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5F5E142C0DDD7100E03EB2 /* RankView.swift */; };
6B5F5E162C0DDD7500E03EB2 /* RankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5F5E142C0DDD7100E03EB2 /* RankView.swift */; };
6B5F5E182C0DDD8700E03EB2 /* RankViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5F5E172C0DDD8700E03EB2 /* RankViewModel.swift */; };
6B5F5E192C0DDD8700E03EB2 /* RankViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5F5E172C0DDD8700E03EB2 /* RankViewModel.swift */; };
6BA5117D2BCFA06F00CB5A54 /* FirstAppearModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA5117C2BCFA06F00CB5A54 /* FirstAppearModifier.swift */; };
6BA5117E2BCFA06F00CB5A54 /* FirstAppearModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA5117C2BCFA06F00CB5A54 /* FirstAppearModifier.swift */; };
6BAAF3472B9B245C00EFE5B2 /* ShimmerEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAAF3442B9B245C00EFE5B2 /* ShimmerEffect.swift */; };
Expand Down Expand Up @@ -4501,6 +4505,8 @@
6B29071E2AF0CB8A006157D6 /* EventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHandler.swift; sourceTree = "<group>"; };
6B5F5E0D2C0C65F700E03EB2 /* MarketPlatformViewNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketPlatformViewNew.swift; sourceTree = "<group>"; };
6B5F5E102C0C660900E03EB2 /* MarketPlatformViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketPlatformViewModel.swift; sourceTree = "<group>"; };
6B5F5E142C0DDD7100E03EB2 /* RankView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankView.swift; sourceTree = "<group>"; };
6B5F5E172C0DDD8700E03EB2 /* RankViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankViewModel.swift; sourceTree = "<group>"; };
6BA5117C2BCFA06F00CB5A54 /* FirstAppearModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAppearModifier.swift; sourceTree = "<group>"; };
6BAAF3442B9B245C00EFE5B2 /* ShimmerEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShimmerEffect.swift; sourceTree = "<group>"; };
6BAAF3452B9B245C00EFE5B2 /* SlideButtonStyling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlideButtonStyling.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7899,6 +7905,7 @@
58AAAC89F3E7CD9F799B89D7 /* Coin */ = {
isa = PBXGroup;
children = (
6B5F5E132C0DDD6000E03EB2 /* Rank */,
D0D6933E270B28240077AF17 /* CoinOverview */,
11B354D11B72A988BA1D8F59 /* CoinMarkets */,
11B35C53664AC4D47BE3EDCA /* Analytics */,
Expand Down Expand Up @@ -7999,6 +8006,15 @@
path = WidgetCoinAppShowWorker;
sourceTree = "<group>";
};
6B5F5E132C0DDD6000E03EB2 /* Rank */ = {
isa = PBXGroup;
children = (
6B5F5E142C0DDD7100E03EB2 /* RankView.swift */,
6B5F5E172C0DDD8700E03EB2 /* RankViewModel.swift */,
);
path = Rank;
sourceTree = "<group>";
};
6BB14F702BFE54F200E879B2 /* Etf */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -10568,6 +10584,7 @@
6BB14F7E2C05FBB000E879B2 /* MarketTvlView.swift in Sources */,
11B350918797E615D4FF6677 /* BlockchainSettingRecordStorage.swift in Sources */,
11B35B7132B99D12DC745064 /* BtcBlockchainSettingsModule.swift in Sources */,
6B5F5E192C0DDD8700E03EB2 /* RankViewModel.swift in Sources */,
6B2907202AF0CB8A006157D6 /* WalletConnectAppShowService.swift in Sources */,
11B35F6B92C2FB142E522828 /* BtcBlockchainSettingsViewModel.swift in Sources */,
11B35CC0D8AC06CE594F84DA /* BtcBlockchainSettingsService.swift in Sources */,
Expand Down Expand Up @@ -10624,6 +10641,7 @@
1A5645CA87E32639CEE6681F /* MarketOverviewGlobalViewModel.swift in Sources */,
1A5642348A701CF7CF5CD805 /* MarketOverviewGlobalDataSource.swift in Sources */,
D3F9B02C2BE3A9A1009FFA95 /* MultiSwapSendView.swift in Sources */,
6B5F5E162C0DDD7500E03EB2 /* RankView.swift in Sources */,
ABC9AB3DAD30AA400DEB719C /* SendBitcoinService.swift in Sources */,
ABC9AD49CCD14F97CD912454 /* SendBitcoinAdapterService.swift in Sources */,
ABC9AF9F8113DB5D54140E7A /* SendBitcoinViewController.swift in Sources */,
Expand Down Expand Up @@ -12149,6 +12167,7 @@
6BB14F7D2C05FBAF00E879B2 /* MarketTvlView.swift in Sources */,
ABC9ABC375B65451761D4766 /* SendFeeViewModel.swift in Sources */,
ABC9A933C2603486BA181B19 /* SendFeeService.swift in Sources */,
6B5F5E182C0DDD8700E03EB2 /* RankViewModel.swift in Sources */,
ABC9A4B643D98FB95F431401 /* SendBitcoinAmountInputService.swift in Sources */,
ABC9ABF99296DEA24FC5BFF0 /* SendAmountCautionService.swift in Sources */,
ABC9AB1E703AE57DF856ECD9 /* SendAmountCautionViewModel.swift in Sources */,
Expand Down Expand Up @@ -12205,6 +12224,7 @@
1A564FC84916FF6D6224FB33 /* MarketOverviewCategoryCell.swift in Sources */,
1A56405220ED225C0B973A7F /* MarketOverviewTopCoinsService.swift in Sources */,
D3F9B02B2BE3A9A1009FFA95 /* MultiSwapSendView.swift in Sources */,
6B5F5E152C0DDD7100E03EB2 /* RankView.swift in Sources */,
1A564A4CF522A7A959482AA6 /* MarketOverviewGlobalService.swift in Sources */,
1A564F3C50FC28F2AF4AF4ED /* MarketOverviewGlobalViewModel.swift in Sources */,
1A5643CB57594E84707686A3 /* MarketOverviewGlobalDataSource.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions UnstoppableWallet/UnstoppableWallet/Extensions/Coin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ extension Coin {
return "https://cdn.blocksdecoded.com/coin-icons/32px/\(uid)@\(scale)x.png"
}
}

extension Coin: Identifiable {
public var id: String { uid }
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,21 @@ extension CoinProChartModule.ProChartType {
}
}

extension CoinRankModule.RankType {
var statRankType: StatPage {
switch self {
case .cexVolume: return .coinRankCexVolume
case .dexVolume: return .coinRankDexVolume
case .dexLiquidity: return .coinRankDexLiquidity
case .address: return .coinRankAddress
case .txCount: return .coinRankTxCount
case .holders: return .coinRankHolders
case .fee: return .coinRankFee
case .revenue: return .coinRankRevenue
}
}
}

extension MarketModule.Top {
var statMarketTop: StatMarketTop {
switch self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ class CoinAnalyticsViewController: ThemeViewController {
}

private func openRanks(type: CoinRankModule.RankType) {
let viewController = CoinRankModule.viewController(type: type)
let viewController = CoinRankModule.newView(type: type)
parentNavigationController?.present(viewController, animated: true)
}

Expand Down
190 changes: 190 additions & 0 deletions UnstoppableWallet/UnstoppableWallet/Modules/Coin/Rank/RankView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import Kingfisher
import MarketKit
import SwiftUI

struct RankView: View {
@StateObject var viewModel: RankViewModel
@StateObject var watchlistViewModel: WatchlistViewModel
@Binding var isPresented: Bool

@State private var presentedCoin: Coin?
@State private var timePeriodSelectorPresented = false

init(isPresented: Binding<Bool>, type: CoinRankModule.RankType) {
_viewModel = StateObject(wrappedValue: RankViewModel(type: type))
_watchlistViewModel = StateObject(wrappedValue: WatchlistViewModel(page: type.statRankType))
_isPresented = isPresented
}

var body: some View {
ThemeNavigationView {
ThemeView {
switch viewModel.state {
case .loading:
VStack(spacing: 0) {
header()
Spacer()
ProgressView()
Spacer()
}
case let .loaded(items):
ThemeList(bottomSpacing: .margin16) {
header()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

list(items: items)
}
case .failed:
VStack(spacing: 0) {
header()

SyncErrorView {
viewModel.sync()
}
}
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("button.close".localized) {
isPresented = false
}
}
}
.sheet(item: $presentedCoin) { coin in
CoinPageViewNew(coinUid: coin.uid).ignoresSafeArea()
.onFirstAppear { stat(page: viewModel.type.statRankType, event: .openCoin(coinUid: coin.uid)) }
}
}
}

@ViewBuilder private func header() -> some View {
VStack(spacing: 0) {
HStack(spacing: .margin32) {
VStack(spacing: .margin8) {
Text(viewModel.type.title.localized).themeHeadline1()
Text(viewModel.type.description.localized).themeSubhead2()
}
.padding(.vertical, .margin12)

KFImage.url(URL(string: viewModel.type.imageUid.headerImageUrl))
.resizable()
.frame(width: 76, height: 108)
}
.padding(.leading, .margin16)

Rectangle()
.fill(Color.themeSteel10)
.frame(height: .heightOneDp)
.frame(maxWidth: .infinity)
}
}

@ViewBuilder private func listHeader(disabled: Bool = false) -> some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
Button(action: {
viewModel.sortOrder.toggle()
}) {
Text(viewModel.type.sortingField.localized)
}
.buttonStyle(SecondaryButtonStyle(style: .default, rightAccessory: .custom(image: sortIcon())))
.disabled(disabled)

if viewModel.timePeriods.count > 1 {
Button(action: {
timePeriodSelectorPresented = true
}) {
Text(viewModel.timePeriod.shortTitle)
}
.buttonStyle(SecondaryButtonStyle(style: .default, rightAccessory: .dropDown))
.disabled(disabled)
}
}
.padding(.horizontal, .margin16)
.padding(.vertical, .margin8)
}
.alert(
isPresented: $timePeriodSelectorPresented,
title: "market.time_period.title".localized,
viewItems: viewModel.timePeriods.map { .init(text: $0.title, selected: viewModel.timePeriod == $0) },
onTap: { index in
guard let index else {
return
}

viewModel.timePeriod = viewModel.timePeriods[index]
}
)
}

@ViewBuilder private func list(items: [RankViewModel.Item]) -> some View {
Section {
ListForEach(items) { item in
let coin = item.coin

ClickableRow(action: {
presentedCoin = item.coin
}) {
itemContent(
index: item.index,
coin: coin,
value: item.value
)
}
.watchlistSwipeActions(viewModel: watchlistViewModel, coinUid: coin.uid)
}
} header: {
listHeader()
.listRowInsets(EdgeInsets())
.background(Color.themeTyler)
}
}

@ViewBuilder private func loadingList() -> some View {
Section {
ListForEach(Array(0 ... 10)) { index in
ListRow {
itemContent(
index: 1,
coin: nil,
value: 12345.45
)
.redacted()
}
}
} header: {
listHeader(disabled: true)
.listRowInsets(EdgeInsets())
.background(Color.themeTyler)
}
}

@ViewBuilder private func itemContent(index: Int, coin: Coin?, value: Decimal) -> some View {
Text(index.description)
.textCaptionSB()
.frame(minWidth: 24, alignment: .center)

CoinIconView(coin: coin)

VStack(alignment: .leading, spacing: 1) {
Text(coin?.code ?? "CODE").textBody()
Text(coin?.name ?? "COIN NAME").textSubhead2()
}

Spacer()
if let formatted = ValueFormatter.instance.formatShort(currency: viewModel.currency, value: value) {
Text(formatted).textBody()
}
}

private func sortIcon() -> Image {
switch viewModel.sortOrder {
case .asc: return Image("arrow_medium_2_up_20")
case .desc: return Image("arrow_medium_2_down_20")
}
}
}
Loading

0 comments on commit 7b97554

Please sign in to comment.