Skip to content

Commit

Permalink
Implement scroll to top for market modules
Browse files Browse the repository at this point in the history
  • Loading branch information
ealymbaev committed Jun 4, 2024
1 parent dc83150 commit 4de7ad3
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 232 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ struct RankView: View {
Spacer()
}
case let .loaded(items):
ThemeList(bottomSpacing: .margin16) {
header()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

list(items: items)
ScrollViewReader { proxy in
ThemeList(bottomSpacing: .margin16, invisibleTopView: true) {
header()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

list(items: items)
}
.onChange(of: viewModel.sortOrder) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
.onChange(of: viewModel.timePeriod) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
}
case .failed:
VStack(spacing: 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@ struct CoinTreasuriesView: View {
switch viewModel.state {
case .loading:
VStack(spacing: 0) {
header()
header(disabled: true)
loadingList()
}
case let .loaded(treasuries):
VStack(spacing: 0) {
header()

ThemeList(bottomSpacing: .margin32) {
list(treasuries: treasuries)
footer()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
ScrollViewReader { proxy in
ThemeList(bottomSpacing: .margin32, invisibleTopView: true) {
list(treasuries: treasuries)
footer()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
}
.onChange(of: viewModel.filter) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
.onChange(of: viewModel.sortOrder) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
}
}
case .failed:
Expand All @@ -41,7 +45,7 @@ struct CoinTreasuriesView: View {
}
}

@ViewBuilder private func header() -> some View {
@ViewBuilder private func header(disabled: Bool = false) -> some View {
HorizontalDivider(color: .themeSteel10)
HStack {
HStack {
Expand All @@ -51,42 +55,46 @@ struct CoinTreasuriesView: View {
Text(viewModel.filter.title)
}
.buttonStyle(SecondaryButtonStyle(style: .transparent, rightAccessory: .dropDown))
.disabled(disabled)
}
.alert(
isPresented: $filterSelectorPresented,
title: "coin_analytics.treasuries.filters".localized,
viewItems: viewModel.filters.map { .init(text: $0.title, selected: viewModel.filter == $0) },
viewItems: CoinTreasuriesViewModel.Filter.allCases.map { .init(text: $0.title, selected: viewModel.filter == $0) },
onTap: { index in
guard let index else {
return
}

viewModel.filter = viewModel.filters[index]
viewModel.filter = CoinTreasuriesViewModel.Filter.allCases[index]
}
)

Spacer()

Button(action: {
viewModel.toggleSortBy()
viewModel.sortOrder.toggle()
}) {
Image(viewModel.orderedAscending ? "sort_l2h_20" : "sort_h2l_20").renderingMode(.template)
sortIcon().renderingMode(.template)
}
.buttonStyle(SecondaryCircleButtonStyle(style: .default))
.padding(.trailing, .margin16)
.disabled(disabled)
}
.padding(.vertical, .margin8)
}

@ViewBuilder private func list(treasuries: [CoinTreasury]) -> some View {
ListForEach(treasuries) { treasury in
itemContent(
logoUrl: treasury.fundLogoUrl,
fund: treasury.fund,
amount: ValueFormatter.instance.formatShort(value: treasury.amount, decimalCount: 8, symbol: viewModel.coinCode) ?? "---",
country: treasury.country,
amountInCurrency: ValueFormatter.instance.formatShort(currency: viewModel.currency, value: treasury.amountInCurrency) ?? "---"
)
ListRow {
itemContent(
imageUrl: URL(string: treasury.fundLogoUrl),
fund: treasury.fund,
amount: ValueFormatter.instance.formatShort(value: treasury.amount, decimalCount: 8, symbol: viewModel.coinCode) ?? "---",
country: treasury.country,
amountInCurrency: ValueFormatter.instance.formatShort(currency: viewModel.currency, value: treasury.amountInCurrency) ?? "---"
)
}
}
}

Expand All @@ -102,39 +110,44 @@ struct CoinTreasuriesView: View {
ThemeList(Array(0 ... 10)) { _ in
ListRow {
itemContent(
logoUrl: nil,
fund: "",
amount: "",
country: "",
amountInCurrency: ""
imageUrl: nil,
fund: "Unstoppable",
amount: "123.45 BTC",
country: "KG",
amountInCurrency: "$123.45"
)
.redacted()
}
}
.simultaneousGesture(DragGesture(minimumDistance: 0), including: .all)
}

@ViewBuilder private func itemContent(logoUrl: String?, fund: String, amount: String, country: String, amountInCurrency: String) -> some View {
ListRow {
if let url = logoUrl.flatMap({ URL(string: $0) }) {
KFImage.url(url)
.resizable()
.frame(width: .iconSize32, height: .iconSize32)
}
@ViewBuilder private func itemContent(imageUrl: URL?, fund: String, amount: String, country: String, amountInCurrency: String) -> some View {
KFImage.url(imageUrl)
.resizable()
.placeholder { RoundedRectangle(cornerRadius: .cornerRadius8).fill(Color.themeSteel20) }
.clipShape(RoundedRectangle(cornerRadius: .cornerRadius8))
.frame(width: .iconSize32, height: .iconSize32)

VStack(spacing: 1) {
HStack(spacing: .margin8) {
Text(fund).textBody()
Spacer()
Text(amount).textBody()
}
VStack(spacing: 1) {
HStack(spacing: .margin8) {
Text(fund).textBody()
Spacer()
Text(amount).textBody()
}

HStack(spacing: .margin8) {
Text(country).textSubhead2()
Spacer()
Text(amountInCurrency).textSubhead2(color: .themeJacob)
}
HStack(spacing: .margin8) {
Text(country).textSubhead2()
Spacer()
Text(amountInCurrency).textSubhead2(color: .themeJacob)
}
}
}

private func sortIcon() -> Image {
switch viewModel.sortOrder {
case .asc: return Image("sort_l2h_20")
case .desc: return Image("sort_h2l_20")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,23 @@ class CoinTreasuriesViewModel: ObservableObject {
private let coin: Coin
private let marketKit = App.shared.marketKit
private let currencyManager = App.shared.currencyManager

private var cancellables = Set<AnyCancellable>()
private var tasks = Set<AnyTask>()

private var internalState: State = .loading {
didSet {
DispatchQueue.main.async { [weak self] in
self?.syncState()
}
syncState()
}
}

@Published var state: State = .loading
@Published var orderedAscending: Bool = false

var filter: Filter = .all {
@Published var filter: Filter = .all {
didSet {
syncState()
}
}

@Published var sortOrder: MarketModule.SortOrder = .desc {
didSet {
syncState()
}
Expand All @@ -44,15 +45,17 @@ class CoinTreasuriesViewModel: ObservableObject {
Task { [weak self, marketKit, coin, currencyManager] in
do {
let treasuries = try await marketKit.treasuries(coinUid: coin.uid, currencyCode: currencyManager.baseCurrency.code)
DispatchQueue.main.async { [weak self] in

await MainActor.run { [weak self] in
self?.internalState = .loaded(treasuries)
}
} catch {
DispatchQueue.main.async { [weak self] in
await MainActor.run { [weak self] in
self?.internalState = .failed(error)
}
}
}.store(in: &tasks)
}
.store(in: &tasks)
}

private func syncState() {
Expand All @@ -70,7 +73,10 @@ class CoinTreasuriesViewModel: ObservableObject {
}
}
.sorted { lhsTreasury, rhsTreasury in
orderedAscending ? lhsTreasury.amount < rhsTreasury.amount : lhsTreasury.amount > rhsTreasury.amount
switch sortOrder {
case .asc: lhsTreasury.amount < rhsTreasury.amount
case .desc: lhsTreasury.amount > rhsTreasury.amount
}
}

state = .loaded(treasuries)
Expand All @@ -89,15 +95,6 @@ extension CoinTreasuriesViewModel {
coin.code
}

var filters: [Filter] {
Filter.allCases
}

func toggleSortBy() {
orderedAscending = !orderedAscending
syncState()
}

func refresh() async {
sync()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@ struct MarketAdvancedSearchResultsView: View {
VStack(spacing: 0) {
header()

ThemeList(viewModel.marketInfos, bottomSpacing: .margin16) { marketInfo in
let coin = marketInfo.fullCoin.coin
ScrollViewReader { proxy in
ThemeList(viewModel.marketInfos, bottomSpacing: .margin16, invisibleTopView: true) { marketInfo in
let coin = marketInfo.fullCoin.coin

ClickableRow(action: {
presentedFullCoin = marketInfo.fullCoin
}) {
itemContent(
coin: coin,
marketCap: marketInfo.marketCap,
price: marketInfo.price.flatMap { ValueFormatter.instance.formatFull(currency: viewModel.currency, value: $0) } ?? "n/a".localized,
rank: marketInfo.marketCapRank,
diff: marketInfo.priceChangeValue(timePeriod: viewModel.timePeriod)
)
ClickableRow(action: {
presentedFullCoin = marketInfo.fullCoin
}) {
itemContent(
coin: coin,
marketCap: marketInfo.marketCap,
price: marketInfo.price.flatMap { ValueFormatter.instance.formatFull(currency: viewModel.currency, value: $0) } ?? "n/a".localized,
rank: marketInfo.marketCapRank,
diff: marketInfo.priceChangeValue(timePeriod: viewModel.timePeriod)
)
}
.watchlistSwipeActions(viewModel: watchlistViewModel, coinUid: coin.uid)
}
.watchlistSwipeActions(viewModel: watchlistViewModel, coinUid: coin.uid)
.onChange(of: viewModel.sortBy) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,24 +108,29 @@ struct MarketCoinsView: View {
}

@ViewBuilder private func list(marketInfos: [MarketInfo]) -> some View {
ThemeList(marketInfos) { marketInfo in
let coin = marketInfo.fullCoin.coin
ScrollViewReader { proxy in
ThemeList(marketInfos, invisibleTopView: true) { marketInfo in
let coin = marketInfo.fullCoin.coin

ClickableRow(action: {
presentedFullCoin = marketInfo.fullCoin
}) {
itemContent(
coin: coin,
marketCap: marketInfo.marketCap,
price: marketInfo.price.flatMap { ValueFormatter.instance.formatFull(currency: viewModel.currency, value: $0) } ?? "n/a".localized,
rank: marketInfo.marketCapRank,
diff: marketInfo.priceChangeValue(timePeriod: viewModel.timePeriod)
)
ClickableRow(action: {
presentedFullCoin = marketInfo.fullCoin
}) {
itemContent(
coin: coin,
marketCap: marketInfo.marketCap,
price: marketInfo.price.flatMap { ValueFormatter.instance.formatFull(currency: viewModel.currency, value: $0) } ?? "n/a".localized,
rank: marketInfo.marketCapRank,
diff: marketInfo.priceChangeValue(timePeriod: viewModel.timePeriod)
)
}
.watchlistSwipeActions(viewModel: watchlistViewModel, coinUid: coin.uid)
}
.watchlistSwipeActions(viewModel: watchlistViewModel, coinUid: coin.uid)
}
.refreshable {
await viewModel.refresh()
.refreshable {
await viewModel.refresh()
}
.onChange(of: viewModel.sortBy) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
.onChange(of: viewModel.top) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
.onChange(of: viewModel.timePeriod) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,22 @@ struct MarketEtfView: View {
Spacer()
}
case let .loaded(etfs):
ThemeList(bottomSpacing: .margin16) {
header()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

chart()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

list(etfs: etfs)
ScrollViewReader { proxy in
ThemeList(bottomSpacing: .margin16, invisibleTopView: true) {
header()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

chart()
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)

list(etfs: etfs)
}
.onChange(of: viewModel.sortBy) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
.onChange(of: viewModel.timePeriod) { _ in withAnimation { proxy.scrollTo(themeListTopViewId) } }
}
case .failed:
VStack(spacing: 0) {
Expand Down
Loading

0 comments on commit 4de7ad3

Please sign in to comment.