- Balances tab name (from Details) - Restore flow - birthday header + optional - Swipe across tabs to change it - Splash screen with bigger steps - Restore flow seed screen, scrollable + nav bar fixes + scroll to see the seed field - Restore seed box to have a placeholder - Fix the suggestions for the restore seed editor - Rectangle around recovery phrase editor is now a shape instead of a full background with padding 1
This commit is contained in:
parent
74a2936b26
commit
3fa10e5147
|
@ -28,9 +28,10 @@ public struct ImportBirthdayView: View {
|
|||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.minimumScaleFactor(0.3)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
TextField(L10n.ImportWallet.optionalBirthday, text: viewStore.bindingForRedactableBirthday(viewStore.birthdayHeight))
|
||||
Text(L10n.ImportWallet.optionalBirthday)
|
||||
|
||||
TextField("", text: viewStore.bindingForRedactableBirthday(viewStore.birthdayHeight))
|
||||
.frame(height: 40)
|
||||
.font(.custom(FontFamily.Archivo.semiBold.name, size: 25))
|
||||
.keyboardType(.numberPad)
|
||||
|
|
|
@ -9,11 +9,17 @@ import SwiftUI
|
|||
import ComposableArchitecture
|
||||
import Generated
|
||||
import UIComponents
|
||||
import Utils
|
||||
|
||||
public struct ImportWalletView: View {
|
||||
private enum InputID: Hashable {
|
||||
case seed
|
||||
}
|
||||
|
||||
var store: ImportWalletStore
|
||||
|
||||
@FocusState private var seedFieldFocused: Bool
|
||||
@FocusState public var isFocused: Bool
|
||||
@State private var message = ""
|
||||
|
||||
public init(store: ImportWalletStore) {
|
||||
self.store = store
|
||||
|
@ -21,62 +27,100 @@ public struct ImportWalletView: View {
|
|||
|
||||
public var body: some View {
|
||||
ScrollView {
|
||||
WithViewStore(store) { viewStore in
|
||||
VStack(alignment: .center) {
|
||||
ZashiIcon()
|
||||
.padding(.vertical, 30)
|
||||
|
||||
Text(L10n.ImportWallet.description)
|
||||
.font(.custom(FontFamily.Archivo.semiBold.name, size: 25))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Text(L10n.ImportWallet.message)
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 14))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 20)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
ImportSeedEditor(store: store)
|
||||
.frame(minWidth: 270)
|
||||
.frame(height: 215)
|
||||
.focused($seedFieldFocused)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .keyboard) {
|
||||
Spacer()
|
||||
|
||||
Button(L10n.General.done.uppercased()) {
|
||||
seedFieldFocused = false
|
||||
ScrollViewReader { value in
|
||||
WithViewStore(store) { viewStore in
|
||||
VStack(alignment: .center) {
|
||||
ZashiIcon()
|
||||
.padding(.vertical, 30)
|
||||
|
||||
Text(L10n.ImportWallet.description)
|
||||
.font(.custom(FontFamily.Archivo.semiBold.name, size: 25))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Text(L10n.ImportWallet.message)
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 14))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 20)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
TextEditor(text: $message)
|
||||
.autocapitalization(.none)
|
||||
.recoveryPhraseShape()
|
||||
.frame(minWidth: 270)
|
||||
.frame(height: 215)
|
||||
.focused($isFocused)
|
||||
.id(InputID.seed)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .keyboard) {
|
||||
Spacer()
|
||||
|
||||
Button(L10n.General.done.uppercased()) {
|
||||
isFocused = false
|
||||
}
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
}
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
}
|
||||
.overlay {
|
||||
if message.isEmpty {
|
||||
HStack {
|
||||
VStack {
|
||||
Text(L10n.ImportWallet.enterPlaceholder)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.suppressed72.color)
|
||||
.onTapGesture {
|
||||
isFocused = true
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading, 10)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.onChange(of: message) { value in
|
||||
viewStore.send(.seedPhraseInputChanged(RedactableString(message)))
|
||||
}
|
||||
.onChange(of: isFocused) { update in
|
||||
withAnimation {
|
||||
if update {
|
||||
value.scrollTo(InputID.seed, anchor: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button(L10n.General.next.uppercased()) {
|
||||
viewStore.send(.updateDestination(.birthday))
|
||||
}
|
||||
|
||||
Button(L10n.General.next.uppercased()) {
|
||||
viewStore.send(.updateDestination(.birthday))
|
||||
.zcashStyle()
|
||||
.frame(width: 236)
|
||||
.disabled(!viewStore.isValidForm)
|
||||
.padding(.top, 50)
|
||||
}
|
||||
.zcashStyle()
|
||||
.frame(width: 236)
|
||||
.disabled(!viewStore.isValidForm)
|
||||
.padding(.top, 50)
|
||||
.padding(.horizontal, 70)
|
||||
.onAppear(perform: { viewStore.send(.onAppear) })
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForDestination(.birthday),
|
||||
destination: { ImportBirthdayView(store: store) }
|
||||
)
|
||||
.alert(store: store.scope(
|
||||
state: \.$alert,
|
||||
action: { .alert($0) }
|
||||
))
|
||||
.zashiBack()
|
||||
}
|
||||
.applyScreenBackground()
|
||||
.padding(.horizontal, 70)
|
||||
.onAppear(perform: { viewStore.send(.onAppear) })
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForDestination(.birthday),
|
||||
destination: { ImportBirthdayView(store: store) }
|
||||
)
|
||||
.alert(store: store.scope(
|
||||
state: \.$alert,
|
||||
action: { .alert($0) }
|
||||
))
|
||||
.zashiBack()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 1)
|
||||
.applyScreenBackground()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// ImportSeedEditor.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 02/25/2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import Generated
|
||||
|
||||
public struct ImportSeedEditor: View {
|
||||
var store: ImportWalletStore
|
||||
|
||||
public var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
TextEditor(text: viewStore.bindingForRedactableSeedPhrase(viewStore.importedSeedPhrase))
|
||||
.autocapitalization(.none)
|
||||
.importSeedEditorModifier(Asset.Colors.primary.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImportSeedEditorModifier: ViewModifier {
|
||||
var backgroundColor = Color.white
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.padding(1)
|
||||
.background(backgroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func importSeedEditorModifier(_ backgroundColor: Color = .white) -> some View {
|
||||
modifier(ImportSeedEditorModifier(backgroundColor: backgroundColor))
|
||||
}
|
||||
}
|
||||
|
||||
struct ImportSeedInputField_Previews: PreviewProvider {
|
||||
static let width: CGFloat = 400
|
||||
static let height: CGFloat = 200
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
ImportSeedEditor(store: .demo)
|
||||
.frame(width: width, height: height)
|
||||
.applyScreenBackground()
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
.previewLayout(.fixed(width: width + 50, height: height + 50))
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ public struct TabsReducer: ReducerProtocol {
|
|||
case account = 0
|
||||
case send
|
||||
case receive
|
||||
case details
|
||||
case balances
|
||||
|
||||
public var title: String {
|
||||
switch self {
|
||||
|
@ -42,8 +42,8 @@ public struct TabsReducer: ReducerProtocol {
|
|||
return L10n.Tabs.send
|
||||
case .receive:
|
||||
return L10n.Tabs.receive
|
||||
case .details:
|
||||
return L10n.Tabs.details
|
||||
case .balances:
|
||||
return L10n.Tabs.balances
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ public struct TabsReducer: ReducerProtocol {
|
|||
return .none
|
||||
|
||||
case .home(.balanceBreakdown):
|
||||
state.selectedTab = .details
|
||||
state.selectedTab = .balances
|
||||
return .none
|
||||
|
||||
case .home:
|
||||
|
|
|
@ -62,8 +62,10 @@ public struct TabsView: View {
|
|||
),
|
||||
tokenName: tokenName
|
||||
)
|
||||
.tag(TabsReducer.State.Tab.details)
|
||||
.tag(TabsReducer.State.Tab.balances)
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.padding(.bottom, 50)
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
|
@ -122,7 +124,7 @@ public struct TabsView: View {
|
|||
.resizable()
|
||||
.frame(width: 62, height: 17)
|
||||
|
||||
case .details:
|
||||
case .balances:
|
||||
Text(L10n.Tabs.balances.uppercased())
|
||||
.font(.custom(FontFamily.Archivo.bold.name, size: 14))
|
||||
}
|
||||
|
|
|
@ -166,10 +166,12 @@ public enum L10n {
|
|||
/// Enter secret
|
||||
/// recovery phrase
|
||||
public static let description = L10n.tr("Localizable", "importWallet.description", fallback: "Enter secret\nrecovery phrase")
|
||||
/// Enter private seed here…
|
||||
public static let enterPlaceholder = L10n.tr("Localizable", "importWallet.enterPlaceholder", fallback: "Enter private seed here…")
|
||||
/// Enter your 24-word seed phrase to restore the associated wallet.
|
||||
public static let message = L10n.tr("Localizable", "importWallet.message", fallback: "Enter your 24-word seed phrase to restore the associated wallet.")
|
||||
/// optional
|
||||
public static let optionalBirthday = L10n.tr("Localizable", "importWallet.optionalBirthday", fallback: "optional")
|
||||
/// (optional)
|
||||
public static let optionalBirthday = L10n.tr("Localizable", "importWallet.optionalBirthday", fallback: "(optional)")
|
||||
/// Wallet Import
|
||||
public static let title = L10n.tr("Localizable", "importWallet.title", fallback: "Wallet Import")
|
||||
public enum Alert {
|
||||
|
@ -189,9 +191,8 @@ public enum L10n {
|
|||
}
|
||||
}
|
||||
public enum Birthday {
|
||||
/// Enter birthday
|
||||
/// height
|
||||
public static let title = L10n.tr("Localizable", "importWallet.birthday.title", fallback: "Enter birthday\nheight")
|
||||
/// Wallet birthday height
|
||||
public static let title = L10n.tr("Localizable", "importWallet.birthday.title", fallback: "Wallet birthday height")
|
||||
}
|
||||
public enum Button {
|
||||
/// Restore
|
||||
|
@ -647,8 +648,6 @@ public enum L10n {
|
|||
public static let account = L10n.tr("Localizable", "tabs.account", fallback: "Account")
|
||||
/// Balances
|
||||
public static let balances = L10n.tr("Localizable", "tabs.balances", fallback: "Balances")
|
||||
/// Details
|
||||
public static let details = L10n.tr("Localizable", "tabs.details", fallback: "Details")
|
||||
/// Receive
|
||||
public static let receive = L10n.tr("Localizable", "tabs.receive", fallback: "Receive")
|
||||
/// Send
|
||||
|
|
|
@ -72,19 +72,20 @@
|
|||
"importWallet.description" = "Enter secret\nrecovery phrase";
|
||||
"importWallet.message" = "Enter your 24-word seed phrase to restore the associated wallet.";
|
||||
"importWallet.button.restoreWallet" = "Restore";
|
||||
"importWallet.birthday.title" = "Enter birthday\nheight";
|
||||
"importWallet.birthday.title" = "Wallet birthday height";
|
||||
"importWallet.seed.valid" = "VALID SEED PHRASE";
|
||||
"importWallet.alert.success.title" = "Success";
|
||||
"importWallet.alert.success.message" = "The wallet has been successfully recovered.";
|
||||
"importWallet.alert.failed.title" = "Failed to restore wallet";
|
||||
"importWallet.alert.failed.message" = "Error: %@ (code: %@)";
|
||||
"importWallet.optionalBirthday" = "optional";
|
||||
"importWallet.optionalBirthday" = "(optional)";
|
||||
"importWallet.enterPlaceholder" = "Enter private seed here…";
|
||||
|
||||
|
||||
// MARK: - Tabs
|
||||
"tabs.account" = "Account";
|
||||
"tabs.send" = "Send";
|
||||
"tabs.receive" = "Receive";
|
||||
"tabs.details" = "Details";
|
||||
"tabs.balances" = "Balances";
|
||||
|
||||
// MARK: - Home Screen
|
||||
|
|
|
@ -79,7 +79,7 @@ final class SplashManager: ObservableObject {
|
|||
let y = screenSize.height + prevHeight
|
||||
|
||||
if (allPoints - i) % 2 == 0 {
|
||||
prevHeight += CGFloat.random(in: 10...40)
|
||||
prevHeight += CGFloat.random(in: 30...70)
|
||||
}
|
||||
|
||||
points.append(CGPoint(x: x, y: y))
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// RecoveryPhraseEditorShape.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 30.10.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Generated
|
||||
|
||||
struct RecoveryPhraseEditorShape: Shape {
|
||||
func path(in rect: CGRect) -> Path {
|
||||
Path { path in
|
||||
path.move(to: CGPoint(x: 0, y: 0))
|
||||
path.addLine(to: CGPoint(x: 0, y: rect.height))
|
||||
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
|
||||
path.addLine(to: CGPoint(x: rect.width, y: 0))
|
||||
path.closeSubpath()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RecoveryPhraseEditorModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.overlay {
|
||||
RecoveryPhraseEditorShape()
|
||||
.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
public func recoveryPhraseShape() -> some View {
|
||||
modifier(RecoveryPhraseEditorModifier())
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
Text("some message")
|
||||
.frame(width: 320, height: 145)
|
||||
.recoveryPhraseShape()
|
||||
}
|
|
@ -22,7 +22,7 @@ class TabsTests: XCTestCase {
|
|||
)
|
||||
|
||||
await store.send(.home(.balanceBreakdown)) { state in
|
||||
state.selectedTab = .details
|
||||
state.selectedTab = .balances
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,12 +97,12 @@ class TabsTests: XCTestCase {
|
|||
|
||||
func testDetailsTabTitle() {
|
||||
var tabsState = TabsReducer.State.placeholder
|
||||
tabsState.selectedTab = .details
|
||||
tabsState.selectedTab = .balances
|
||||
|
||||
XCTAssertEqual(
|
||||
tabsState.selectedTab.title,
|
||||
L10n.Tabs.details,
|
||||
"Name of the details tab should be '\(L10n.Tabs.details)' but received \(tabsState.selectedTab.title)"
|
||||
L10n.Tabs.balances,
|
||||
"Name of the balances tab should be '\(L10n.Tabs.balances)' but received \(tabsState.selectedTab.title)"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue