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

State button #48

Merged
merged 11 commits into from
Jun 13, 2024
6 changes: 5 additions & 1 deletion Gem.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
481152C82B566AC200377C75 /* Gemstone in Frameworks */ = {isa = PBXBuildFile; productRef = 481152C72B566AC200377C75 /* Gemstone */; };
832553982C0F27F000C9CA0C /* ChartScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832553972C0F27F000C9CA0C /* ChartScene.swift */; };
8325539A2C0FB11C00C9CA0C /* CurrencySceneViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832553992C0FB11C00C9CA0C /* CurrencySceneViewModelTests.swift */; };
83A704272C17CA78005B77F4 /* BuyAssetInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A704262C17CA78005B77F4 /* BuyAssetInputViewModel.swift */; };
83D824022C173FAF0023CA0C /* NetworkSelectorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D824012C173FAF0023CA0C /* NetworkSelectorViewModel.swift */; };
83D824042C176A0D0023CA0C /* ChainFilterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D824032C176A0D0023CA0C /* ChainFilterable.swift */; };
C30952B4299C39D70004C0F9 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30952B3299C39D70004C0F9 /* App.swift */; };
Expand Down Expand Up @@ -253,6 +254,7 @@
487A27E82B632CCE00BEEADB /* Gemstone */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Gemstone; sourceTree = "<group>"; };
832553972C0F27F000C9CA0C /* ChartScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartScene.swift; sourceTree = "<group>"; };
832553992C0FB11C00C9CA0C /* CurrencySceneViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencySceneViewModelTests.swift; sourceTree = "<group>"; };
83A704262C17CA78005B77F4 /* BuyAssetInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyAssetInputViewModel.swift; sourceTree = "<group>"; };
83D824012C173FAF0023CA0C /* NetworkSelectorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSelectorViewModel.swift; sourceTree = "<group>"; };
83D824032C176A0D0023CA0C /* ChainFilterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainFilterable.swift; sourceTree = "<group>"; };
C30952B0299C39D70004C0F9 /* Gem.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gem.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -799,11 +801,11 @@
C366791B2A1860E500F1D74D /* QRScanner */,
C36679142A0FF81000F1D74D /* GemAPI */,
C3549B4C29CA3A9000B4BE01 /* Store */,
C3CF3BD929BBE40300E96586 /* Style */,
C3CF3BA529B5262100E96586 /* Keystore */,
C3CF3BA229B3F29C00E96586 /* WalletCore */,
C3CF3B7329A98C3400E96586 /* Blockchain */,
C3CF3B7529A98C5D00E96586 /* Primitives */,
C3CF3BD929BBE40300E96586 /* Style */,
C3CF3B8629AC2B5500E96586 /* Components */,
C3D1C4E32A2AB5B6006E8EEA /* Signer */,
);
Expand Down Expand Up @@ -1023,6 +1025,7 @@
isa = PBXGroup;
children = (
C3CF3BC629B7CB4900E96586 /* BuyAssetViewModel.swift */,
83A704262C17CA78005B77F4 /* BuyAssetInputViewModel.swift */,
C3D1C50B2A44FD2B006E8EEA /* FiatQuoteViewModel.swift */,
C36679122A0E249100F1D74D /* FiatProvidersViewModel.swift */,
);
Expand Down Expand Up @@ -1842,6 +1845,7 @@
C3549B3B29C5167100B4BE01 /* WalletDetailScene.swift in Sources */,
D8D203BB2ACC878200261CA2 /* ChartService.swift in Sources */,
D810F3742AF1AB520006D9C6 /* StakeScene.swift in Sources */,
83A704272C17CA78005B77F4 /* BuyAssetInputViewModel.swift in Sources */,
C3E99BBA2A70739D005DF35F /* TransactionView.swift in Sources */,
C34C7D132A006E9F009EEC21 /* SecretPhraseViewableModel.swift in Sources */,
C3A7CB8829CD8D3100431341 /* WalletService.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Gem/Assets/Scenes/AddTokenScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct AddTokenScene: View {
Button(Localized.Wallet.Import.action, action: onSelectImportToken)
.disabled(model.state.isLoading)
.frame(maxWidth: Spacing.scene.button.maxWidth)
.buttonStyle(BlueButton())
.buttonStyle(.blue())
}
.padding(.bottom, Spacing.scene.bottom)
.background(Colors.grayBackground)
Expand Down
6 changes: 5 additions & 1 deletion Gem/Assets/Scenes/SelectAssetScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ struct SelectAssetScene: View {
)
)
case .buy:
BuyAssetScene(model: BuyAssetViewModel(assetAddress: input.assetAddress))
BuyAssetScene(
model: BuyAssetViewModel(
assetAddress: input.assetAddress,
input: .default)
)
case .swap:
SwapScene(model: SwapViewModel(
wallet: model.wallet,
Expand Down
1 change: 1 addition & 0 deletions Gem/Assets/ViewModels/AddTokenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SwiftUI
import Settings
import Components

// TODO: - SelectAssetSceneNavigationStack, inject chain & address in SelectAssetSceneNavigationStack
class AddTokenViewModel: ObservableObject {
let wallet: Wallet
let service: AddTokenService
Expand Down
2 changes: 1 addition & 1 deletion Gem/Connections/Scenes/ConnectionProposalScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct ConnectionProposalScene: View {
Text(model.buttonTitle)
}
}
.buttonStyle(BlueButton())
.buttonStyle(.blue())
.padding(.bottom, Spacing.scene.bottom)
.frame(maxWidth: Spacing.scene.button.maxWidth)

Expand Down
2 changes: 1 addition & 1 deletion Gem/Connections/Scenes/SignMessageScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct SignMessageScene: View {
Text(model.buttonTitle)
}
}
.buttonStyle(BlueButton())
.buttonStyle(.blue())
.padding(.bottom, Spacing.scene.bottom)
.frame(maxWidth: Spacing.scene.button.maxWidth)
}
Expand Down
248 changes: 152 additions & 96 deletions Gem/Fiat/Scenes/BuyAssetScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,126 +4,182 @@ import Style
import Components

struct BuyAssetScene: View {

@StateObject var model: BuyAssetViewModel

var body: some View {
VStack {
List {
Section { } header: {
VStack(alignment: .center) {
AmountView(
title: "$\(Int(model.amount))",
subtitle: model.cryptoAmountText(quote: model.quote)
)

ZStack(alignment: .center) {
Grid(alignment: .center) {
ForEach(model.amounts, id: \.self) { amounts in
GridRow(alignment: .center) {
ForEach(amounts, id: \.self) { amount in
VStack(alignment: .center) {
Button("$\(amount)") {
Task {
await buy(amount)
}
}
.buttonStyle(BlueButton(paddingHorizontal: 12, paddingVertical: 12))
.font(.callout)
}
}
}
.padding(.all, 4)
}
}
}
.padding(.top, 8)
}
.padding(.top, 20)
}
.frame(maxWidth: .infinity)
.textCase(nil)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets())

Section {
if let quote = model.quote {
if model.quotes.count > 1 {
NavigationLink(value: Scenes.FiatProviders()) {
ListItemView(title: Localized.Common.provider, subtitle: quote.provider.name)
}
} else {
ListItemView(title: Localized.Common.provider, subtitle: quote.provider.name)
}
ListItemView(title: Localized.Buy.rate, subtitle: model.rateText(quote: quote))
} else if let quoteError = model.quoteError {
Text(quoteError.localizedDescription)
.multilineTextAlignment(.center)
} else if model.quoteLoading == false {
Text(Localized.Buy.noResults)
.multilineTextAlignment(.center)
}
} header: {
VStack {
if model.quoteLoading {
ProgressView()
.progressViewStyle(.circular)
}
}
.frame(maxWidth: .infinity)
}
amountSelectorSection
providerSection
}
Spacer()

Button(Localized.Common.continue, action: {
buyNext()
})
.disabled(model.quote == nil)
.padding(.bottom, Spacing.scene.bottom)
StatefullButton(
text: Localized.Common.continue,
viewState: model.state,
action: openBuyUrl
)
.disabled(model.shouldDisalbeContinueButton)
.frame(maxWidth: Spacing.scene.button.maxWidth)
.buttonStyle(BlueButton())
}
.padding(.bottom, Spacing.scene.bottom)
.background(Colors.grayBackground)
.frame(maxWidth: .infinity)
.navigationTitle(model.title)
.task {
guard model.quote == nil else {
return
}
await model.getQuotes(asset: model.asset, amount: Double(model.defaultaAmount))
await onTask()
}
.navigationDestination(for: Scenes.FiatProviders.self) {_ in
FiatProvidersScene(
model: FiatProvidersViewModel(
currentQuote: model.quote!,
buyAssetInput: model.input,
asset: model.asset,
quotes: model.quotes,
selectQuote: {
model.quote = $0
}
selectQuote: onSelectQuote(_:)
)
)
}
.frame(maxWidth: .infinity)
}

func buy(_ amount: Int) async {
await model.getQuotes(asset: model.asset, amount: Double(amount))
}

// MARK: - UI Components

extension BuyAssetScene {
private var amountSelectorSection: some View {
Section {

} header: {
amountSelectorView
.padding(.top, 20)
}
.textCase(nil)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets())
}

func buyNext() {
guard
let quote = model.quote,
let url = URL(string: quote.redirectUrl) else { return }


@ViewBuilder
private var providerSection: some View {
Section {
if !model.state.isLoading {
providerView
}
} header: {
VStack {
if model.state.isLoading {
ProgressView()
.progressViewStyle(.circular)
}
}
.frame(maxWidth: .infinity)
}
}

@ViewBuilder
private var providerView: some View {
switch model.state {
case .noData:
Text(Localized.Buy.noResults)
.multilineTextAlignment(.center)
case .loading:
EmptyView()
case .loaded:
if let quote = model.input.quote {
if model.input.quotes.count > 1 {
NavigationLink(value: Scenes.FiatProviders()) {
ListItemView(title: Localized.Common.provider, subtitle: quote.provider.name)
}
} else {
ListItemView(title: Localized.Common.provider, subtitle: quote.provider.name)
}
ListItemView(title: Localized.Buy.rate, subtitle: model.rateText(for: quote))
} else {
EmptyView()
}
case .error(let error):
Text(error.localizedDescription)
.multilineTextAlignment(.center)
}
}

@ViewBuilder
private var amountSelectorView: some View {
VStack(alignment: .center) {
AmountView(
title: "$\(Int(model.amount))",
subtitle: model.cryptoAmountText(for: model.input.quote)
)

ZStack(alignment: .center) {
Grid(alignment: .center) {
ForEach(model.amounts, id: \.self) { amounts in
GridRow(alignment: .center) {
ForEach(amounts, id: \.self) { amount in
VStack(alignment: .center) {
Button("$\(Int(amount))") {
onSelectNew(amount: amount)
}
.buttonStyle(.blue(paddingHorizontal: 12, paddingVertical: 12))
.font(.callout)
}
}
}
.padding(.all, 4)
}
}
}
.padding(.top, 8)
}
}
}

// MARK: - Actions

extension BuyAssetScene {
private func onTask() async {
guard model.input.quote == nil else { return }
await getQuotesAsync(amount: model.amount)
}

private func onSelectContinue() {
openBuyUrl()
}

private func onSelectNew(amount: Double) {
getQuotes(amount: amount)
}

private func onSelectQuote(_ quote: FiatQuote) {
model.input.quote = quote
}
}

// MARK: - Effects

extension BuyAssetScene {
private func getQuotes(amount: Double) {
Task {
await getQuotesAsync(amount: amount)
}
}

private func getQuotesAsync(amount: Double) async {
await model.getQuotes(for: model.asset, amount: amount)
}

private func openBuyUrl() {
guard let quote = model.input.quote,
let url = URL(string: quote.redirectUrl) else { return }

// TODO: - use new @Environment(\.openURL) var openURL, insead use UIKit UIApplication
UIApplication.shared.open(url, options: [:])
}
}

//struct BuyAssetScene_Previews: PreviewProvider {
// static var previews: some View {
// NavigationStack {
// BuyAssetScene(model: BuyAssetViewModel(assetAddress: <#T##AssetAddress#>))
// }.navigationBarTitleDisplayMode(.inline)
// }
//}
// MARK: - Previews

#Preview {
@StateObject var model = BuyAssetViewModel(assetAddress: .init(asset: .main, address: .empty), input: .default)

return NavigationStack {
BuyAssetScene(model: model)
.navigationBarTitleDisplayMode(.inline)
}
}
Loading
Loading