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

Wallet Details screen added delete button #52

Closed
wants to merge 18 commits into from
Closed
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
138 changes: 94 additions & 44 deletions Gem/Wallets/Scenes/WalletDetailScene.swift
Original file line number Diff line number Diff line change
@@ -1,61 +1,75 @@
import Foundation
import Keystore
import SwiftUI
import Components
import Style

struct WalletDetailScene: View {

let model: WalletDetailViewModel


@Environment(\.dismiss) private var dismiss

@State private var name: String

// next
@State var words: [String]? = nil
@State private var words: [String]? = nil

@State private var isPresentingErrorMessage: String?
@State private var isPresentingDeleteConfirmation: Bool?

init(model: WalletDetailViewModel) {
self.model = model
self.name = model.name
_name = State(initialValue: self.model.name)
}

var body: some View {
List {
Section {
FloatTextField(Localized.Wallet.name, text: $name)
}
switch model.wallet.type {
case .multicoin, .single:
VStack {
List {
Section {
NavigationCustomLink(
with: ListItemView(title: Localized.Common.show(Localized.Common.secretPhrase))
) {
Task {
do {
self.words = try model.keystore.getMnemonic(wallet: model.wallet)
} catch {
isPresentingErrorMessage = error.localizedDescription
}
}
}
} header: {
Text(Localized.Common.secretPhrase)
FloatTextField(Localized.Wallet.name, text: $name)
}
case .view:
EmptyView()
}
Section {
switch model.address {
case .account(let account):
AddressListItem(title: Localized.Common.address, style: .short, account: account)
case .none:
switch model.wallet.type {
case .multicoin, .single:
Section {
NavigationCustomLink(
with: ListItemView(title: Localized.Common.show(Localized.Common.secretPhrase)),
action: onShowSecretPhrase
)
} header: {
Text(Localized.Common.secretPhrase)
}
case .view:
EmptyView()
}
Section {
switch model.address {
case .account(let account):
AddressListItem(title: Localized.Common.address, style: .short, account: account)
case .none:
EmptyView()
}
}
}

Spacer()
Button(Localized.Common.delete, action: onSelectDelete)
.buttonStyle(.delete())
.frame(maxWidth: Spacing.scene.button.maxWidth)
}
.padding(.bottom, Spacing.scene.bottom)
.background(Colors.grayBackground)
.frame(maxWidth: .infinity)
.onChange(of: name, onChangeWalletName)
.navigationTitle(model.title)
.onChange(of: name) {
try? model.rename(name: name)
}
.confirmationDialog(
Localized.Wallet.deleteWalletConfirmation(model.name),
presenting: $isPresentingDeleteConfirmation,
sensoryFeedback: .warning,
actions: { _ in
Button(
Localized.Common.delete,
role: .destructive,
action: onDeleteWallet
)
}
)
.alert(item: $isPresentingErrorMessage) {
Alert(title: Text(Localized.Errors.transfer("")), message: Text($0))
}
Expand All @@ -65,10 +79,46 @@ struct WalletDetailScene: View {
}
}

//struct WalletInfoScene_Previews: PreviewProvider {
// static var previews: some View {
// WalletInfoScene(
// model: WalletDetailViewModel(wallet: .main, keystore: LocalKeystore.main)
// )
// }
//}
// MARK: - Actions

extension WalletDetailScene {
private func onChangeWalletName() {
do {
try model.rename(name: name)
} catch {
isPresentingErrorMessage = error.localizedDescription
}
}

private func onShowSecretPhrase() {
Task {
do {
words = try await model.getMnemonicWords()
} catch {
isPresentingErrorMessage = error.localizedDescription
}
}
}

private func onSelectDelete() {
isPresentingDeleteConfirmation = true
}

private func onDeleteWallet() {
do {
try model.delete()
dismiss()
} catch {
isPresentingErrorMessage = error.localizedDescription
}
}
}

// MARK: - Previews

#Preview {
NavigationStack {
WalletDetailScene(model: .init(wallet: .main, keystore: LocalKeystore.main))
.toolbarTitleDisplayMode(.inline)
}
}
31 changes: 21 additions & 10 deletions Gem/Wallets/ViewModels/WalletIDetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Primitives
import Keystore

struct WalletDetailViewModel {

let wallet: Wallet
let keystore: any Keystore

Expand All @@ -15,19 +14,12 @@ struct WalletDetailViewModel {
return Localized.Common.wallet
}

func rename(name: String) throws {
try keystore.renameWallet(wallet: wallet, newName: name)
}

var address: WalletDetailAddress? {
switch wallet.type {
case .multicoin:
return .none
case .single,
.view:
guard let account = wallet.accounts.first else {
return .none
}
case .single, .view:
guard let account = wallet.accounts.first else { return .none }
return WalletDetailAddress.account(
SimpleAccount(
name: .none,
Expand All @@ -39,3 +31,22 @@ struct WalletDetailViewModel {
}
}

// MARK: - Business Logic

extension WalletDetailViewModel {
func rename(name: String) throws {
try keystore.renameWallet(wallet: wallet, newName: name)
}

func getMnemonicWords() async throws -> [String] {
try keystore.getMnemonic(wallet: wallet)
}

func delete() throws {
try keystore.deleteWallet(for: wallet)

if keystore.wallets.isEmpty {
try CleanUpService(keystore: keystore).onDeleteAllWallets()
}
}
}
61 changes: 61 additions & 0 deletions Packages/Components/Sources/Extensions/SwiftUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,65 @@ public extension View {
self
}
}

@ViewBuilder
func `if`<Content: View>(
_ condition: Bool,
content: (Self) -> Content
) -> some View {
self.ifElse(condition, ifContent: content, elseContent: { _ in self })
}

@ViewBuilder
func ifElse<TrueContent: View, FalseContent: View>(
_ condition: Bool,
ifContent: (Self) -> TrueContent,
elseContent: (Self) -> FalseContent
) -> some View {
if condition {
ifContent(self)
} else {
elseContent(self)
}
}
}

// MARK: - Confirmation Dialog

public extension View {
func confirmationDialog<S, A, T>(
_ title: S,
presenting data: Binding<T?>,
sensoryFeedback: SensoryFeedback,
@ViewBuilder actions: (T) -> A)
-> some View where S: StringProtocol, A: View {
let isPresented: Binding<Bool> = Binding(
get: {
return data.wrappedValue != nil
}, set: { newValue in
guard !newValue else { return }
data.wrappedValue = nil
}
)
// confiramtion dialog works good only for iPhone, for different devices use a alert
let iPhone = UIDevice.current.userInterfaceIdiom == .phone

return ifElse(iPhone) {
$0.confirmationDialog(
title,
isPresented: isPresented,
titleVisibility: .visible,
presenting: data.wrappedValue,
actions: actions
)
} elseContent: {
$0.alert(
title,
isPresented: isPresented,
presenting: data.wrappedValue,
actions: actions
)
}
}
}

18 changes: 18 additions & 0 deletions Packages/Style/Sources/ButtonStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ extension ButtonStyle where Self == ColorButtonStyle {
backgroundPressed: Colors.grayVeryLight
)
}

public static func delete(
paddingHorizontal: CGFloat = Spacing.medium,
paddingVertical: CGFloat = Spacing.medium) -> ColorButtonStyle {
return ColorButtonStyle(
paddingHorizontal: paddingHorizontal,
paddingVertical: paddingVertical,
foregroundStyle: Colors.red,
foregroundStylePressed: Colors.red,
background: Colors.whiteGrayBackground,
backgroundPressed: Colors.whiteGrayBackground
)
}
}

public struct ClearButtonStyle: ButtonStyle {
Expand Down Expand Up @@ -283,6 +296,11 @@ extension ButtonStyle where Self == StatefulButtonStyle {
}
.buttonStyle(.white())

Button(action: {}) {
Text("Delete Button")
}
.buttonStyle(.delete())

Button(action: {}) {
Text("Clear Button")
}
Expand Down
2 changes: 2 additions & 0 deletions Packages/Style/Sources/Colors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public struct Colors {
public static let grayBackground = Color.dynamicColor("#F2F2F7", dark: "#1C1C1E")
public static let grayDarkBackground = Color.dynamicColor("#E6E6F0", dark: "#1C1C1E")
public static let secondaryText = Color.dynamicColor("#818181")
public static let whiteGrayBackground = Color.dynamicColor("#FFFFFF", dark: "#2C2C2E")
}

#Preview {
Expand All @@ -37,6 +38,7 @@ public struct Colors {
("Gray Background", Colors.grayBackground),
("Gray Dark Background", Colors.grayDarkBackground),
("Secondary Text", Colors.secondaryText),
("WhiteGray", Colors.whiteGrayBackground),
]
return List {
ForEach(colors, id: \.name) { color in
Expand Down
Loading