Skip to content

Commit

Permalink
State button (#48)
Browse files Browse the repository at this point in the history
* Refactored BuyAssetScene logic to support state button
added view state, removed bipolared code
added input mode
updated body to separate components
updated AddTokenViewModel & WalletScene to support default input for BuyAssetScene
updated FiatProvidersViewModel to support buyAsset input

* ButtonStyle refactored, updated gem dependecies with new buttonstyle api
* Added State Button
Added new state style for button style
Updated previews for colors, system image, button style, state empty view => using List now
Updates in BuyAssetScene & BuyAssetViewModel to support state button

* Git submodules refreshed
  • Loading branch information
gemdev111 authored Jun 13, 2024
1 parent 7c703e1 commit af7103a
Show file tree
Hide file tree
Showing 31 changed files with 732 additions and 421 deletions.
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

0 comments on commit af7103a

Please sign in to comment.