[#830] Zashi design buttons (#837)

- all features of the button done
- cleanup of everything that is not part of the design anymore, needed for the colors for the buttons
- zcashStyle used for the buttons
This commit is contained in:
Lukas Korba 2023-10-04 11:04:22 +02:00 committed by GitHub
parent e1a5fa9778
commit e33ee405ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
207 changed files with 320 additions and 7869 deletions

View File

@ -35,7 +35,6 @@ let package = Package(
.library(name: "Pasteboard", targets: ["Pasteboard"]),
.library(name: "Profile", targets: ["Profile"]),
.library(name: "RecoveryPhraseDisplay", targets: ["RecoveryPhraseDisplay"]),
.library(name: "RecoveryPhraseValidationFlow", targets: ["RecoveryPhraseValidationFlow"]),
.library(name: "ReviewRequest", targets: ["ReviewRequest"]),
.library(name: "Root", targets: ["Root"]),
.library(name: "Sandbox", targets: ["Sandbox"]),
@ -317,20 +316,6 @@ let package = Package(
],
path: "Sources/Features/RecoveryPhraseDisplay"
),
.target(
name: "RecoveryPhraseValidationFlow",
dependencies: [
"FeedbackGenerator",
"Generated",
"Models",
"Pasteboard",
"UIComponents",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/RecoveryPhraseValidationFlow"
),
.target(
name: "ReviewRequest",
dependencies: [
@ -355,7 +340,6 @@ let package = Package(
"Models",
"OnboardingFlow",
"RecoveryPhraseDisplay",
"RecoveryPhraseValidationFlow",
"Sandbox",
"SDKSynchronizer",
"UIComponents",

View File

@ -26,7 +26,7 @@ public struct BalanceBreakdownView: View {
HStack {
Spacer()
Text(L10n.BalanceBreakdown.blockId(viewStore.latestBlock))
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
}
.padding(.horizontal, 50)
.padding(.vertical, 20)
@ -34,7 +34,7 @@ public struct BalanceBreakdownView: View {
balanceView(
title: L10n.BalanceBreakdown.shieldedZec(tokenName),
viewStore.shieldedBalance.data.verified,
titleColor: Asset.Colors.Mfp.fontDark.color
titleColor: Asset.Colors.primary.color
)
balanceView(title: L10n.BalanceBreakdown.transparentBalance, viewStore.transparentBalance.data.verified)
balanceView(title: L10n.BalanceBreakdown.totalSpendableBalance, viewStore.totalSpendableBalance)
@ -49,7 +49,7 @@ public struct BalanceBreakdownView: View {
tokenName
)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
}
.padding(.horizontal, 50)
@ -67,7 +67,7 @@ public struct BalanceBreakdownView: View {
}
extension BalanceBreakdownView {
func balanceView(title: String, _ balance: Zatoshi, titleColor: Color = Asset.Colors.Mfp.fontDark.color) -> some View {
func balanceView(title: String, _ balance: Zatoshi, titleColor: Color = Asset.Colors.primary.color) -> some View {
VStack(alignment: .leading) {
Text("\(title)")
.foregroundColor(titleColor)
@ -81,7 +81,7 @@ extension BalanceBreakdownView {
.custom(FontFamily.Inter.regular.name, size: 32)
.weight(.bold)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
}
.padding(.horizontal, 50)
}
@ -93,18 +93,18 @@ extension BalanceBreakdownView {
if viewStore.shieldingFunds {
HStack(spacing: 10) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: Asset.Colors.Text.activeButtonText.color))
Text(L10n.BalanceBreakdown.shieldingFunds)
.progressViewStyle(CircularProgressViewStyle(tint: Asset.Colors.primary.color))
Text(L10n.BalanceBreakdown.shieldingFunds.uppercased())
}
} else {
Text(L10n.BalanceBreakdown.shieldFunds)
}
}
)
.activeButtonStyle
.padding(.horizontal, 50)
.zcashStyle()
.padding(.horizontal, 70)
.padding(.vertical, 20)
.disable(when: !viewStore.isShieldableBalanceAvailable || viewStore.shieldingFunds, dimmingOpacity: 0.5)
.disabled(!viewStore.isShieldableBalanceAvailable || viewStore.shieldingFunds)
}
}

View File

@ -32,7 +32,7 @@ public struct HomeView: View {
viewStore.send(.updateDestination(.transactionHistory))
} label: {
Text(L10n.Home.transactionHistory)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
}
}
.padding()
@ -99,30 +99,29 @@ extension HomeView {
SettingsView(store: store.settingsStore())
}
)
.tint(Asset.Colors.Mfp.primary.color)
.tint(Asset.Colors.primary.color)
}
func sendButton(_ viewStore: HomeViewStore) -> some View {
Button(action: {
viewStore.send(.updateDestination(.send))
}, label: {
Text(L10n.Home.sendZec(tokenName))
Text(L10n.Home.sendZec(tokenName).uppercased())
})
.activeButtonStyle
.zcashStyle()
.padding(.horizontal, 70)
.padding(.bottom, 30)
.disable(
when: viewStore.isSendButtonDisabled,
dimmingOpacity: 0.5
)
.disabled(viewStore.isSendButtonDisabled)
}
func receiveButton(_ viewStore: HomeViewStore) -> some View {
Button(action: {
viewStore.send(.updateDestination(.profile))
}, label: {
Text(L10n.Home.receiveZec(tokenName))
Text(L10n.Home.receiveZec(tokenName).uppercased())
})
.activeButtonStyle
.zcashStyle()
.padding(.horizontal, 70)
.padding(.bottom, 30)
}
@ -154,7 +153,7 @@ extension HomeView {
}
}
}
.foregroundColor(Asset.Colors.Mfp.primary.color)
.foregroundColor(Asset.Colors.primary.color)
}
}

View File

@ -25,7 +25,7 @@ public struct ImportBirthdayView: View {
.custom(FontFamily.Inter.regular.name, size: 16)
.weight(.bold)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
TextField(
L10n.ImportWallet.Birthday.placeholder,
@ -35,15 +35,13 @@ public struct ImportBirthdayView: View {
.autocapitalization(.none)
.importSeedEditorModifier()
Button(L10n.ImportWallet.Button.restoreWallet) {
Button(L10n.ImportWallet.Button.restoreWallet.uppercased()) {
viewStore.send(.restoreWallet)
}
.activeButtonStyle
.zcashStyle()
.padding(.horizontal, 70)
.importWalletButtonLayout()
.disable(
when: !viewStore.isValidForm,
dimmingOpacity: 0.5
)
.disabled(!viewStore.isValidForm)
Spacer()
}

View File

@ -25,21 +25,19 @@ public struct ImportWalletView: View {
.custom(FontFamily.Inter.regular.name, size: 27)
.weight(.bold)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
.minimumScaleFactor(0.3)
ImportSeedEditor(store: store)
.frame(width: nil, height: 200, alignment: .center)
Button(L10n.General.next) {
Button(L10n.General.next.uppercased()) {
viewStore.send(.updateDestination(.birthday))
}
.activeButtonStyle
.zcashStyle()
.padding(.horizontal, 70)
.importWalletButtonLayout()
.disable(
when: !viewStore.isValidForm,
dimmingOpacity: 0.5
)
.disabled(!viewStore.isValidForm)
Spacer()
}
@ -71,8 +69,8 @@ extension ImportWalletView {
)
.foregroundColor(
viewStore.isValidNumberOfWords ?
Asset.Colors.Text.validMnemonic.color :
Asset.Colors.Text.heading.color
Asset.Colors.primary.color :
Asset.Colors.primary.color
)
.padding(.trailing, 35)
.padding(.bottom, 15)

View File

@ -16,7 +16,7 @@ public struct ImportSeedEditor: View {
WithViewStore(store) { viewStore in
TextEditor(text: viewStore.bindingForRedactableSeedPhrase(viewStore.importedSeedPhrase))
.autocapitalization(.none)
.importSeedEditorModifier(Asset.Colors.Mfp.fontDark.color)
.importSeedEditorModifier(Asset.Colors.primary.color)
.padding(.horizontal, 28)
.padding(.vertical, 10)
}
@ -28,7 +28,7 @@ struct ImportSeedEditorModifier: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
.padding(1)
.background(backgroundColor)
}

View File

@ -24,58 +24,27 @@ public struct OnboardingFlowReducer: ReducerProtocol {
case createNewWallet
case importExistingWallet
}
public struct Step: Equatable, Identifiable {
public let id: UUID
public let title: String
public let description: String
public let background: Image
}
public var destination: Destination?
public var walletConfig: WalletConfig
public var importWalletState: ImportWalletReducer.State
public var index = 0
public var skippedAtindex: Int?
public var steps: IdentifiedArrayOf<Step> = Self.onboardingSteps
public var currentStep: Step { steps[index] }
public var isFinalStep: Bool { steps.count == index + 1 }
public var isInitialStep: Bool { index == 0 }
public var progress: Int { ((index + 1) * 100) / (steps.count) }
public var offset: CGFloat {
let maxOffset = CGFloat(-60)
let stepOffset = CGFloat(maxOffset / CGFloat(steps.count - 1))
guard index != 0 else { return .zero }
return stepOffset * CGFloat(index)
}
public init(
destination: Destination? = nil,
walletConfig: WalletConfig,
importWalletState: ImportWalletReducer.State,
index: Int = 0,
skippedAtindex: Int? = nil,
steps: IdentifiedArrayOf<Step> = Self.onboardingSteps
importWalletState: ImportWalletReducer.State
) {
self.destination = destination
self.walletConfig = walletConfig
self.importWalletState = importWalletState
self.index = index
self.skippedAtindex = skippedAtindex
self.steps = steps
}
}
public enum Action: Equatable {
case back
case createNewWallet
case importExistingWallet
case importWallet(ImportWalletReducer.Action)
case next
case onAppear
case skip
case updateDestination(OnboardingFlowReducer.State.Destination?)
}
@ -91,30 +60,6 @@ public struct OnboardingFlowReducer: ReducerProtocol {
Reduce { state, action in
switch action {
case .onAppear:
if !state.walletConfig.isEnabled(.onboardingFlow) {
return EffectTask(value: .skip)
}
return .none
case .back:
guard state.index > 0 else { return .none }
if let skippedFrom = state.skippedAtindex {
state.index = skippedFrom
state.skippedAtindex = nil
} else {
state.index -= 1
}
return .none
case .next:
guard state.index < state.steps.count - 1 else { return .none }
state.index += 1
return .none
case .skip:
guard state.skippedAtindex == nil else { return .none }
state.skippedAtindex = state.index
state.index = state.steps.count - 1
return .none
case .updateDestination(let destination):
@ -136,37 +81,6 @@ public struct OnboardingFlowReducer: ReducerProtocol {
}
}
extension OnboardingFlowReducer.State {
public static let onboardingSteps = IdentifiedArray(
uniqueElements: [
Step(
id: UUID(),
title: L10n.Onboarding.Step1.title,
description: L10n.Onboarding.Step1.description,
background: Asset.Assets.Backgrounds.callout1.image
),
Step(
id: UUID(),
title: L10n.Onboarding.Step2.title,
description: L10n.Onboarding.Step2.description,
background: Asset.Assets.Backgrounds.callout2.image
),
Step(
id: UUID(),
title: L10n.Onboarding.Step3.title,
description: L10n.Onboarding.Step3.description,
background: Asset.Assets.Backgrounds.callout3.image
),
Step(
id: UUID(),
title: L10n.Onboarding.Step4.title,
description: L10n.Onboarding.Step4.description,
background: Asset.Assets.Backgrounds.callout4.image
)
]
)
}
// MARK: - ViewStore
extension OnboardingFlowViewStore {

View File

@ -1,98 +0,0 @@
//
// OnboardingScreen.swift
// secant-testnet
//
// Created by Adam Stener on 11/7/21.
//
import SwiftUI
import ComposableArchitecture
import UIComponents
public struct OnboardingScreen: View {
let store: OnboardingFlowStore
public init(store: OnboardingFlowStore) {
self.store = store
}
public var body: some View {
WithViewStore(store) { viewStore in
VStack {
ZStack {
OnboardingHeaderView(
store: store.scope(
state: { state in
OnboardingHeaderView.ViewState(
walletConfig: state.walletConfig,
isInitialStep: state.isInitialStep,
isFinalStep: state.isFinalStep
)
},
action: { action in
switch action {
case .back: return .back
case .skip: return .skip
}
}
)
)
.zIndex(1)
OnboardingContentView(store: store)
}
Spacer()
OnboardingFooterView(store: store)
Spacer()
}
.navigationBarHidden(true)
.applyScreenBackground()
.onAppear { viewStore.send(.onAppear) }
}
}
}
// MARK: - Previews
struct OnboardingScreen_Previews: PreviewProvider {
static var previews: some View {
OnboardingScreen(
store: Store(
initialState: OnboardingFlowReducer.State(
walletConfig: .default,
importWalletState: .placeholder
),
reducer: OnboardingFlowReducer(saplingActivationHeight: 0)
)
)
.preferredColorScheme(.light)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
OnboardingScreen(
store: Store(
initialState: OnboardingFlowReducer.State(
walletConfig: .default,
importWalletState: .placeholder
),
reducer: OnboardingFlowReducer(saplingActivationHeight: 0)
)
)
.preferredColorScheme(.light)
.previewDevice(PreviewDevice(rawValue: "iPhone 8"))
OnboardingScreen(
store: Store(
initialState: OnboardingFlowReducer.State(
walletConfig: .default,
importWalletState: .placeholder
),
reducer: OnboardingFlowReducer(saplingActivationHeight: 0)
)
)
.preferredColorScheme(.light)
.previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro"))
}
}

View File

@ -38,16 +38,19 @@ public struct PlainOnboardingView: View {
Spacer()
Button(L10n.PlainOnboarding.Button.createNewWallet) {
Button(L10n.PlainOnboarding.Button.createNewWallet.uppercased()) {
viewStore.send(.createNewWallet, animation: .easeInOut(duration: animationDuration))
}
.activeButtonStyle
Button(L10n.PlainOnboarding.Button.restoreWallet) {
.zcashStyle()
.padding(.horizontal, 70)
.padding(.bottom, 30)
Button(L10n.PlainOnboarding.Button.restoreWallet.uppercased()) {
viewStore.send(.importExistingWallet, animation: .easeInOut(duration: animationDuration))
}
.frame(height: 80)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.zcashStyle(.secondary)
.padding(.horizontal, 70)
.padding(.bottom, 20)
}
.padding(.all)
.navigationLinkEmpty(

View File

@ -1,115 +0,0 @@
//
// OnboardingContentView.swift
// secant-testnet
//
// Created by Adam Stener on 11/18/21.
//
import SwiftUI
import ComposableArchitecture
public struct OnboardingContentView: View {
let store: OnboardingFlowStore
public init(store: OnboardingFlowStore) {
self.store = store
}
public var body: some View {
WithViewStore(self.store) { viewStore in
let image = viewStore.steps[viewStore.index].background
.resizable()
.scaledToFit()
let title = Text(viewStore.steps[viewStore.index].title)
.titleText()
.lineLimit(0)
.minimumScaleFactor(0.1)
.padding(.vertical, 10)
let text = Text(viewStore.steps[viewStore.index].description)
.paragraphText()
.lineSpacing(2)
.minimumScaleFactor(0.1)
.padding(.horizontal, 20)
if viewStore.isFinalStep {
VStack {
HStack {
title
.padding(.top, 60)
Spacer()
}
.padding(.horizontal, 20)
text
image
}
} else {
VStack {
image
HStack {
title
Spacer()
}
.padding(.horizontal, 20)
text
}
}
}
}
}
struct OnboardingContentView_Previews: PreviewProvider {
static var previews: some View {
let store = Store(
initialState: OnboardingFlowReducer.State(
walletConfig: .default,
importWalletState: .placeholder,
index: 0
),
reducer: OnboardingFlowReducer(saplingActivationHeight: 0)
)
OnboardingContentView_Previews.example(store)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
OnboardingContentView_Previews.example(store)
.previewDevice(PreviewDevice(rawValue: "iPhone 8"))
OnboardingContentView_Previews.example(store)
.previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro"))
}
}
// MARK: - Previews
extension OnboardingContentView_Previews {
static func example(_ store: Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>) -> some View {
ZStack {
OnboardingHeaderView(
store: store.scope(
state: { state in
OnboardingHeaderView.ViewState(
walletConfig: state.walletConfig,
isInitialStep: state.isInitialStep,
isFinalStep: state.isFinalStep
)
},
action: { action in
switch action {
case .back: return .back
case .skip: return .skip
}
}
)
)
.zIndex(1)
OnboardingContentView(
store: store
)
}
.applyScreenBackground()
.preferredColorScheme(.light)
}
}

View File

@ -1,116 +0,0 @@
//
// OnboardingFooterView.swift
// secant-testnet
//
// Created by Adam Stener on 11/18/21.
//
import SwiftUI
import ComposableArchitecture
import Generated
import ImportWallet
public struct OnboardingFooterView: View {
let store: OnboardingFlowStore
let animationDuration: CGFloat = 0.8
public init(store: OnboardingFlowStore) {
self.store = store
}
public var body: some View {
WithViewStore(self.store) { viewStore in
VStack(spacing: 5) {
if viewStore.isFinalStep {
Button(L10n.Onboarding.Button.newWallet) {
viewStore.send(.createNewWallet, animation: .easeInOut(duration: animationDuration))
}
.activeButtonStyle
.minimumScaleFactor(0.1)
Button(L10n.Onboarding.Button.importWallet) {
viewStore.send(.importExistingWallet, animation: .easeInOut(duration: animationDuration))
}
.activeButtonStyle
.minimumScaleFactor(0.1)
} else {
Button(L10n.General.next) {
viewStore.send(.next, animation: .easeInOut(duration: animationDuration))
}
.activeButtonStyle
.minimumScaleFactor(0.1)
ProgressView(
String(format: "%02d", viewStore.index + 1),
value: Double(viewStore.index + 1),
total: Double(viewStore.steps.count)
)
.onboardingProgressStyle
.padding(.horizontal, 30)
.padding(.vertical, 20)
}
}
.padding(.top, 10)
.padding(.horizontal, 30)
.navigationLinkEmpty(
isActive: viewStore.bindingForDestination(.importExistingWallet),
destination: {
ImportWalletView(
store: store.scope(
state: \.importWalletState,
action: OnboardingFlowReducer.Action.importWallet
)
)
}
)
}
}
}
// swiftlint:disable:next private_over_fileprivate strict_fileprivate
fileprivate struct OnboardingFooterButtonLayout: ViewModifier {
func body(content: Content) -> some View {
content
.frame(height: 60)
.padding(.horizontal, 28)
.transition(.opacity)
}
}
extension View {
func onboardingFooterButtonLayout() -> some View {
modifier(OnboardingFooterButtonLayout())
}
}
// MARK: - Previews
struct OnboardingFooterView_Previews: PreviewProvider {
static var previews: some View {
let store = Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>(
initialState: OnboardingFlowReducer.State(
walletConfig: .default,
importWalletState: .placeholder,
index: 3
),
reducer: OnboardingFlowReducer(saplingActivationHeight: 0)
)
Group {
OnboardingFooterView(store: store)
.applyScreenBackground()
.preferredColorScheme(.light)
.previewDevice("iPhone 14 Pro")
OnboardingFooterView(store: store)
.applyScreenBackground()
.preferredColorScheme(.light)
.previewDevice("iPhone 13 Pro Max")
OnboardingFooterView(store: store)
.applyScreenBackground()
.preferredColorScheme(.light)
.previewDevice("iPhone 13 mini")
}
}
}

View File

@ -1,100 +0,0 @@
//
// OnboardingNavigationButtons.swift
// secant-testnet
//
// Created by Adam Stener on 11/18/21.
//
import SwiftUI
import ComposableArchitecture
import Generated
import Models
public struct OnboardingHeaderView: View {
public struct ViewState: Equatable {
public let walletConfig: WalletConfig
public let isInitialStep: Bool
public let isFinalStep: Bool
}
public enum ViewAction {
case back
case skip
}
let store: Store<ViewState, ViewAction>
let animationDuration: CGFloat = 0.8
public init(store: Store<ViewState, ViewAction>) {
self.store = store
}
public var body: some View {
WithViewStore(self.store) { viewStore in
VStack {
HStack {
if !viewStore.isInitialStep && viewStore.walletConfig.isEnabled(.onboardingFlow) {
Button(L10n.General.back) {
viewStore.send(.back, animation: .easeInOut(duration: animationDuration))
}
.activeButtonStyle
.frame(width: 75)
.disabled(viewStore.isInitialStep)
.minimumScaleFactor(0.1)
}
Spacer()
if !viewStore.isInitialStep && !viewStore.isFinalStep {
Button(L10n.General.skip) {
viewStore.send(.skip, animation: .easeInOut(duration: animationDuration))
}
.activeButtonStyle
.disabled(viewStore.isFinalStep)
.frame(width: 150)
.minimumScaleFactor(0.1)
}
}
.padding(.horizontal, 30)
.frame(height: 40)
Spacer()
}
}
}
}
// MARK: - Previews
struct OnboardingHeaderView_Previews: PreviewProvider {
static var previews: some View {
let store = Store<OnboardingFlowReducer.State, OnboardingFlowReducer.Action>(
initialState: OnboardingFlowReducer.State(
walletConfig: .default,
importWalletState: .placeholder,
index: 0
),
reducer: OnboardingFlowReducer(saplingActivationHeight: 0)
)
OnboardingHeaderView(
store: store.scope(
state: { state in
OnboardingHeaderView.ViewState(
walletConfig: state.walletConfig,
isInitialStep: state.isInitialStep,
isFinalStep: state.isFinalStep
)
},
action: { action in
switch action {
case .back: return .back
case .skip: return .skip
}
}
)
)
.preferredColorScheme(.light)
.applyScreenBackground()
}
}

View File

@ -1,102 +0,0 @@
//
// OnboardingProgressIndicator.swift
// secant-testnet
//
// Created by Adam Stener on 10/15/21.
//
import SwiftUI
import Generated
struct OnboardingProgressStyle: ProgressViewStyle {
let height: CGFloat = 3
let gradient = LinearGradient(
colors: [
Asset.Colors.ProgressIndicator.gradientLeft.color,
Asset.Colors.ProgressIndicator.gradientRight.color
],
startPoint: .leading,
endPoint: .trailing
)
func makeBody(configuration: Configuration) -> some View {
let fractionCompleted = configuration.fractionCompleted ?? 0
return VStack {
HStack {
configuration.label
.foregroundColor(Asset.Colors.Text.heading.color)
.font(
.custom(FontFamily.Inter.regular.name, size: 16)
)
.opacity(0.3)
Spacer()
}
ZStack {
GeometryReader { proxy in
let currentWidth = proxy.size.width
let progressMaxWidth = currentWidth * CGFloat(fractionCompleted)
let trailingMaxWidth = currentWidth - (currentWidth * CGFloat(fractionCompleted))
HStack(spacing: 15) {
if fractionCompleted > 0 {
Capsule()
.fill(gradient)
.frame(maxWidth: progressMaxWidth)
}
if fractionCompleted < 1 {
Capsule()
.fill(Asset.Colors.ProgressIndicator.negativeSpace.color)
.frame(maxWidth: trailingMaxWidth)
}
}
}
.frame(height: height)
.animation(.easeInOut, value: fractionCompleted)
}
}
}
}
// MARK: - ProgressView : onboardingProgressStyle
extension ProgressView {
public var onboardingProgressStyle: some View {
progressViewStyle(OnboardingProgressStyle())
}
}
// MARK: - Interactive ProgressStyle View
struct OnboardingProgressViewPreviewHelper: View {
@State private var value: CGFloat = 35.0
var progressString: String {
String(format: "%02d", value)
}
var body: some View {
VStack(spacing: 50) {
ProgressView(
"\(Int(value))",
value: value,
total: 100
)
.onboardingProgressStyle
Slider(value: $value, in: 0...100, step: 1)
}
.padding(.horizontal)
}
}
// MARK: - Previews
struct OnboardingProgressIndicator_Previews: PreviewProvider {
static var previews: some View {
OnboardingProgressViewPreviewHelper()
}
}

View File

@ -68,10 +68,10 @@ public struct RecoveryPhraseDisplayView: View {
VStack {
Button(
action: { viewStore.send(.finishedPressed) },
label: { Text(L10n.RecoveryPhraseDisplay.Button.wroteItDown) }
label: { Text(L10n.RecoveryPhraseDisplay.Button.wroteItDown.uppercased()) }
)
.activeButtonStyle
.frame(height: 60)
.zcashStyle()
.padding(.horizontal, 70)
}
.padding()
} else {

View File

@ -1,29 +0,0 @@
//
// RecoveryPhraseRandomizer.swift
// secant-testnet
//
// Created by Lukáš Korba on 01.06.2022.
//
import Foundation
import Models
public struct RecoveryPhraseRandomizer {
public func random(phrase: RecoveryPhrase) -> RecoveryPhraseValidationFlowReducer.State {
let missingIndices = randomIndices()
let missingWordChipKind = phrase.words(fromMissingIndices: missingIndices).shuffled()
return RecoveryPhraseValidationFlowReducer.State(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: missingWordChipKind,
validationWords: []
)
}
public func randomIndices() -> [Int] {
(0..<RecoveryPhraseValidationFlowReducer.State.phraseChunks).map { _ in
Int.random(in: 0 ..< RecoveryPhraseValidationFlowReducer.State.wordGroupSize)
}
}
}

View File

@ -1,24 +0,0 @@
//
// RecoveryPhraseRandomizerInterface.swift
// secant-testnet
//
// Created by Lukáš Korba on 15.11.2022.
//
import ComposableArchitecture
import Models
extension DependencyValues {
public var randomRecoveryPhrase: RecoveryPhraseRandomizerClient {
get { self[RecoveryPhraseRandomizerClient.self] }
set { self[RecoveryPhraseRandomizerClient.self] = newValue }
}
}
public struct RecoveryPhraseRandomizerClient {
public var random: (RecoveryPhrase) -> RecoveryPhraseValidationFlowReducer.State
public init(random: @escaping (RecoveryPhrase) -> RecoveryPhraseValidationFlowReducer.State) {
self.random = random
}
}

View File

@ -1,15 +0,0 @@
//
// RecoveryPhraseRandomizerLiveKey.swift
// secant-testnet
//
// Created by Lukáš Korba on 15.11.2022.
//
import Foundation
import ComposableArchitecture
extension RecoveryPhraseRandomizerClient: DependencyKey {
public static let liveValue = Self(
random: { RecoveryPhraseRandomizer().random(phrase: $0) }
)
}

View File

@ -1,15 +0,0 @@
//
// RecoveryPhraseRandomizerTestKey.swift
// secant-testnet
//
// Created by Lukáš Korba on 15.11.2022.
//
import ComposableArchitecture
import XCTestDynamicOverlay
extension RecoveryPhraseRandomizerClient: TestDependencyKey {
public static let testValue = Self(
random: XCTUnimplemented("\(Self.self).random", placeholder: .placeholder)
)
}

View File

@ -1,326 +0,0 @@
//
// RecoveryPhraseValidation.swift
// secant-testnet
//
// Created by Francisco Gindre on 10/29/21.
//
import Foundation
import ComposableArchitecture
import SwiftUI
import Utils
import FeedbackGenerator
import UIComponents
import Models
import Pasteboard
public typealias RecoveryPhraseValidationFlowStore = Store<RecoveryPhraseValidationFlowReducer.State, RecoveryPhraseValidationFlowReducer.Action>
public typealias RecoveryPhraseValidationFlowViewStore = ViewStore<RecoveryPhraseValidationFlowReducer.State, RecoveryPhraseValidationFlowReducer.Action>
public struct RecoveryPhraseValidationFlowReducer: ReducerProtocol {
public struct State: Equatable {
public enum Destination: Equatable, CaseIterable {
case validation
case success
case failure
}
static let wordGroupSize = 6
static let phraseChunks = 4
public var phrase: RecoveryPhrase
public var missingIndices: [Int]
public var missingWordChips: [PhraseChip.Kind]
public var validationWords: [ValidationWord]
public var destination: Destination?
public var isComplete: Bool {
!validationWords.isEmpty && validationWords.count == missingIndices.count
}
public var isValid: Bool {
guard let resultingPhrase = self.resultingPhrase else { return false }
return resultingPhrase == phrase.words
}
public init(phrase: RecoveryPhrase, missingIndices: [Int], missingWordChips: [PhraseChip.Kind], validationWords: [ValidationWord], destination: Destination? = nil) {
self.phrase = phrase
self.missingIndices = missingIndices
self.missingWordChips = missingWordChips
self.validationWords = validationWords
self.destination = destination
}
}
public enum Action: Equatable {
case updateDestination(RecoveryPhraseValidationFlowReducer.State.Destination?)
case reset
case move(wordChip: PhraseChip.Kind, intoGroup: Int)
case succeed
case fail
case failureFeedback
case proceedToHome
case displayBackedUpPhrase
}
@Dependency(\.feedbackGenerator) var feedbackGenerator
@Dependency(\.mainQueue) var mainQueue
@Dependency(\.pasteboard) var pasteboard
@Dependency(\.randomRecoveryPhrase) var randomRecoveryPhrase
public init() {}
// swiftlint:disable:next cyclomatic_complexity
public func reduce(into state: inout State, action: Action) -> ComposableArchitecture.EffectTask<Action> {
switch action {
case .reset:
state = randomRecoveryPhrase.random(state.phrase)
state.destination = .validation
// FIXME [#186]: Resetting causes destination to be nil = preamble screen, hence setting the .validation. The transition back is not animated
// though
case let .move(wordChip, group):
guard
case let PhraseChip.Kind.unassigned(word, _) = wordChip,
let missingChipIndex = state.missingWordChips.firstIndex(of: wordChip)
else { return .none }
state.missingWordChips[missingChipIndex] = .empty
state.validationWords.append(ValidationWord(groupIndex: group, word: word))
if state.isComplete {
let value: RecoveryPhraseValidationFlowReducer.Action = state.isValid ? .succeed : .fail
let effect = EffectTask<RecoveryPhraseValidationFlowReducer.Action>(value: value)
.delay(for: 1, scheduler: mainQueue)
.eraseToEffect()
if value == .succeed {
return effect
} else {
return .concatenate(
EffectTask(value: .failureFeedback),
effect
)
}
}
return .none
case .succeed:
state.destination = .success
case .fail:
state.destination = .failure
case .failureFeedback:
feedbackGenerator.generateErrorFeedback()
case .updateDestination(let destination):
guard let destination else {
state = randomRecoveryPhrase.random(state.phrase)
return .none
}
state.destination = destination
case .proceedToHome:
break
case .displayBackedUpPhrase:
break
}
return .none
}
}
extension RecoveryPhraseValidationFlowReducer.State {
func groupCompleted(index: Int) -> Bool {
validationWords.first(where: { $0.groupIndex == index }) != nil
}
}
extension RecoveryPhraseValidationFlowReducer.State {
/// Given an array of RecoveryPhraseStepCompletion, missing indices, original phrase and the number of groups it was split into,
/// assembly the resulting phrase. This comes up with the "proposed solution" for the recovery phrase validation challenge.
/// - returns:an array of String containing the recovery phrase words ordered by the original phrase order, or `nil`
/// if a resulting phrase can't be formed because the validation state is not complete.
public var resultingPhrase: [RedactableString]? {
guard missingIndices.count == validationWords.count else { return nil }
guard validationWords.count == Self.phraseChunks else { return nil }
var words = phrase.words
let groupLength = words.count / Self.phraseChunks
// iterate based on the completions the user did on the UI
for validationWord in validationWords {
// figure out which phrase group (chunk) this completion belongs to
let groupIndex = validationWord.groupIndex
// validate that's the right number
assert(groupIndex < Self.phraseChunks)
// get the missing index that the user did this completion for on the given group
let missingIndex = missingIndices[groupIndex]
// figure out what this means in terms of the whole recovery phrase
let concreteIndex = groupIndex * groupLength + missingIndex
assert(concreteIndex < words.count)
// replace the word on the copy of the original phrase with the completion the user did
words[concreteIndex] = validationWord.word
}
return words
}
}
// MARK: - ViewStore
extension RecoveryPhraseValidationFlowViewStore {
func bindingForDestination(_ destination: RecoveryPhraseValidationFlowReducer.State.Destination) -> Binding<Bool> {
self.binding(
get: { $0.destination == destination },
send: { isActive in
return .updateDestination(isActive ? destination : nil)
}
)
}
}
extension RecoveryPhraseValidationFlowViewStore {
var bindingForValidation: Binding<Bool> {
self.binding(
get: { $0.destination != nil },
send: { isActive in
return .updateDestination(isActive ? .validation : nil)
}
)
}
var bindingForSuccess: Binding<Bool> {
self.binding(
get: { $0.destination == .success },
send: { isActive in
return .updateDestination(isActive ? .success : .validation)
}
)
}
var bindingForFailure: Binding<Bool> {
self.binding(
get: { $0.destination == .failure },
send: { isActive in
return .updateDestination(isActive ? .failure : .validation)
}
)
}
}
// MARK: - Placeholders
extension RecoveryPhraseValidationFlowReducer.State {
public static let placeholder = RecoveryPhraseValidationFlowReducer.State(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.unassigned(word: "thank".redacted),
.unassigned(word: "morning".redacted),
.unassigned(word: "boil".redacted),
.unassigned(word: "garlic".redacted)
],
validationWords: [],
destination: nil
)
public static let placeholderStep1 = RecoveryPhraseValidationFlowReducer.State(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.unassigned(word: "thank".redacted),
.empty,
.unassigned(word: "boil".redacted),
.unassigned(word: "garlic".redacted)
],
validationWords: [
.init(groupIndex: 2, word: "morning".redacted)
],
destination: nil
)
public static let placeholderStep2 = RecoveryPhraseValidationFlowReducer.State(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.unassigned(word: "boil".redacted),
.unassigned(word: "garlic".redacted)
],
validationWords: [
.init(groupIndex: 2, word: "morning".redacted),
.init(groupIndex: 0, word: "thank".redacted)
],
destination: nil
)
public static let placeholderStep3 = RecoveryPhraseValidationFlowReducer.State(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.unassigned(word: "boil".redacted),
.empty
],
validationWords: [
.init(groupIndex: 2, word: "morning".redacted),
.init(groupIndex: 0, word: "thank".redacted),
.init(groupIndex: 3, word: "garlic".redacted)
],
destination: nil
)
public static let placeholderStep4 = RecoveryPhraseValidationFlowReducer.State(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.empty,
.empty
],
validationWords: [
.init(groupIndex: 2, word: "morning".redacted),
.init(groupIndex: 0, word: "thank".redacted),
.init(groupIndex: 3, word: "garlic".redacted),
.init(groupIndex: 1, word: "boil".redacted)
],
destination: nil
)
}
extension RecoveryPhraseValidationFlowStore {
static let demo = Store(
initialState: .placeholder,
reducer: RecoveryPhraseValidationFlowReducer()
)
static let demoStep1 = Store(
initialState: .placeholderStep1,
reducer: RecoveryPhraseValidationFlowReducer()
)
static let demoStep2 = Store(
initialState: .placeholderStep1,
reducer: RecoveryPhraseValidationFlowReducer()
)
static let demoStep3 = Store(
initialState: .placeholderStep3,
reducer: RecoveryPhraseValidationFlowReducer()
)
static let demoStep4 = Store(
initialState: .placeholderStep4,
reducer: RecoveryPhraseValidationFlowReducer()
)
}

View File

@ -1,119 +0,0 @@
//
// RecoveryPhraseValidationFlowView.swift
// secant-testnet
//
// Created by Lukáš Korba on 03/01/22.
//
import SwiftUI
import ComposableArchitecture
import Generated
public struct RecoveryPhraseValidationFlowView: View {
var store: RecoveryPhraseValidationFlowStore
public init(store: RecoveryPhraseValidationFlowStore) {
self.store = store
}
public var body: some View {
WithViewStore(store) { viewStore in
GeometryReader { proxy in
VStack {
VStack(alignment: .center, spacing: 20) {
Text(L10n.RecoveryPhraseTestPreamble.title)
.titleText()
.multilineTextAlignment(.center)
Text(L10n.RecoveryPhraseTestPreamble.paragraph1)
.paragraphText()
.multilineTextAlignment(.center)
.padding(.horizontal, 44)
.opacity(0.53)
}
.padding(.bottom, 40)
Asset.Assets.Backgrounds.calloutBackupFlow1.image
.frame(
width: circularFrameUniformSize(width: proxy.size.width, height: proxy.size.height),
height: circularFrameUniformSize(width: proxy.size.width, height: proxy.size.height)
)
Spacer()
VStack(alignment: .center, spacing: 40) {
VStack(alignment: .center, spacing: 20) {
Text(L10n.RecoveryPhraseTestPreamble.paragraph2)
.paragraphText()
.multilineTextAlignment(.center)
.opacity(0.53)
Text(L10n.RecoveryPhraseTestPreamble.paragraph3)
.paragraphText()
.multilineTextAlignment(.center)
.padding(.horizontal, 10)
.opacity(0.53)
}
Button(
action: { viewStore.send(.updateDestination(.validation)) },
label: { Text(L10n.RecoveryPhraseTestPreamble.Button.goNext) }
)
.activeButtonStyle
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 64,
maxHeight: .infinity,
alignment: .center
)
.fixedSize(horizontal: false, vertical: true)
}
.padding()
Spacer()
}
.frame(width: proxy.size.width)
.scrollableWhenScaledUp()
.navigationLinkEmpty(
isActive: viewStore.bindingForValidation,
destination: {
RecoveryPhraseBackupView(store: store)
}
)
}
.padding()
.navigationBarHidden(true)
.applyScreenBackground()
}
}
}
/// Following computations are necessary to handle properly sizing and positioning of elements
/// on different devices (aspects). iPhone SE and iPhone 8 are similar aspect family devices
/// while iPhone X, 11, etc are different family devices, capable to use more of the space.
extension RecoveryPhraseValidationFlowView {
func circularFrameUniformSize(width: CGFloat, height: CGFloat) -> CGFloat {
var deviceMultiplier = 1.0
if width > 0.0 {
let aspect = height / width
deviceMultiplier = 1.0 + (((aspect / 1.51) - 1.0) * 2.8)
}
return width * 0.4 * deviceMultiplier
}
}
struct RecoveryPhraseTestPreambleView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
RecoveryPhraseValidationFlowView(store: .demo)
}
RecoveryPhraseValidationFlowView(store: .demo)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
}
}
}

View File

@ -1,114 +0,0 @@
//
// ValidationFailed.swift
// secant-testnet
//
// Created by Francisco Gindre on 12/22/21.
//
import SwiftUI
import ComposableArchitecture
import Generated
import Utils
import UIComponents
public struct RecoveryPhraseBackupFailedView: View {
@Environment(\.presentationMode) var presentationMode
var store: RecoveryPhraseValidationFlowStore
public init(store: RecoveryPhraseValidationFlowStore) {
self.store = store
}
public var body: some View {
WithViewStore(store) { viewStore in
GeometryReader { proxy in
VStack {
VStack(alignment: .center, spacing: 20) {
Text(L10n.ValidationFailed.title)
.titleText()
.multilineTextAlignment(.center)
}
.padding(.bottom, 40)
Asset.Assets.Backgrounds.calloutBackupFailed.image
.frame(
width: circularFrameUniformSize(width: proxy.size.width, height: proxy.size.height),
height: circularFrameUniformSize(width: proxy.size.width, height: proxy.size.height)
)
Spacer()
VStack(alignment: .center, spacing: 40) {
VStack(alignment: .center, spacing: 20) {
Text(L10n.ValidationFailed.description)
.paragraphText()
.multilineTextAlignment(.center)
.padding(.horizontal, 30)
Text(L10n.ValidationFailed.incorrectBackupDescription)
.paragraphText()
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
}
Button(
action: {
viewStore.send(.reset)
presentationMode.wrappedValue.dismiss()
},
label: { Text(L10n.ValidationFailed.Button.tryAgain) }
)
.activeButtonStyle
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 64,
maxHeight: .infinity,
alignment: .center
)
.fixedSize(horizontal: false, vertical: true)
.padding(.horizontal, 28)
}
Spacer()
}
.scrollableWhenScaledUp()
}
.padding()
.navigationBarHidden(true)
.applyScreenBackground()
}
}
}
/// Following computations are necessary to handle properly sizing and positioning of elements
/// on different devices (aspects). iPhone SE and iPhone 8 are similar aspect family devices
/// while iPhone X, 11, etc are different family devices, capable to use more of the space.
extension RecoveryPhraseBackupFailedView {
func circularFrameUniformSize(width: CGFloat, height: CGFloat) -> CGFloat {
var deviceMultiplier = 1.0
if width > 0.0 {
let aspect = height / width
deviceMultiplier = 1.0 + (((aspect / 1.51) - 1.0) * 2.0)
}
return width * 0.48 * deviceMultiplier
}
}
// MARK: - Previews
struct RecoveryPhraseBackupValidationFailedView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
RecoveryPhraseBackupFailedView(store: .demo)
}
RecoveryPhraseBackupFailedView(store: .demo)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
}
}
}

View File

@ -1,137 +0,0 @@
//
// SuccessView.swift
// secant-testnet
//
// Created by Adam Stener on 12/8/21.
//
import SwiftUI
import ComposableArchitecture
import Generated
public struct RecoveryPhraseBackupSucceededView: View {
var store: RecoveryPhraseValidationFlowStore
public init(store: RecoveryPhraseValidationFlowStore) {
self.store = store
}
public var body: some View {
WithViewStore(store) { viewStore in
GeometryReader { proxy in
VStack {
VStack(spacing: 20) {
Text(L10n.ValidationSuccess.title)
.titleText()
.multilineTextAlignment(.center)
Text(L10n.ValidationSuccess.description)
.paragraphText()
.multilineTextAlignment(.center)
.padding(.horizontal, 45)
}
.padding(.vertical, 40)
VStack {
Asset.Assets.Backgrounds.calloutBackupSucceeded.image
.frame(
width: circularFrameUniformSize(width: proxy.size.width, height: proxy.size.height),
height: circularFrameUniformSize(width: proxy.size.width, height: proxy.size.height)
)
}
.padding(.bottom, 40)
Spacer()
VStack(spacing: 15) {
Button(
action: {
viewStore.send(.proceedToHome, animation: .easeIn(duration: 1))
},
label: {
Text(L10n.ValidationSuccess.Button.goToWallet)
.fixedSize(horizontal: false, vertical: true)
}
)
.activeButtonStyle
.recoveryPhraseBackupValidationSucceededViewLayout()
Button(
action: {
viewStore.send(
.displayBackedUpPhrase,
animation: .easeIn(duration: 1)
)
},
label: {
Text(L10n.ValidationSuccess.Button.phraseAgain)
.fixedSize(horizontal: false, vertical: true)
}
)
.activeButtonStyle
.recoveryPhraseBackupValidationSucceededViewLayout()
}
}
.padding(.horizontal)
.scrollableWhenScaledUp()
}
}
.navigationBarHidden(true)
.applyScreenBackground()
}
}
/// Following computations are necessary to handle properly sizing and positioning of elements
/// on different devices (aspects). iPhone SE and iPhone 8 are similar aspect family devices
/// while iPhone X, 11, etc are different family devices, capable to use more of the space.
extension RecoveryPhraseBackupSucceededView {
func circularFrameUniformSize(width: CGFloat, height: CGFloat) -> CGFloat {
var deviceMultiplier = 1.0
if width > 0.0 {
let aspect = height / width
deviceMultiplier = 1.0 + (((aspect / 1.51) - 1.0) * 2.0)
}
return width * 0.48 * deviceMultiplier
}
}
// swiftlint:disable:next private_over_fileprivate strict_fileprivate type_name
fileprivate struct RecoveryPhraseBackupValidationSucceededViewLayout: ViewModifier {
func body(content: Content) -> some View {
content
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 64,
maxHeight: .infinity,
alignment: .center
)
.fixedSize(horizontal: false, vertical: true)
.padding(.horizontal, 28)
.transition(.opacity)
}
}
extension View {
func recoveryPhraseBackupValidationSucceededViewLayout() -> some View {
modifier(RecoveryPhraseBackupValidationSucceededViewLayout())
}
}
// MARK: - Previews
// swiftlint:disable:next type_name
struct RecoveryPhraseBackupValidationSucceededView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
RecoveryPhraseBackupSucceededView(store: .demo)
}
RecoveryPhraseBackupSucceededView(store: .demo)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
}
}
}

View File

@ -1,191 +0,0 @@
//
// RecoveryPhraseBackupView.swift
// secant-testnet
//
// Created by Francisco Gindre on 10/29/21.
//
import SwiftUI
import ComposableArchitecture
import Generated
import UIComponents
import Models
public struct RecoveryPhraseBackupView: View {
let store: RecoveryPhraseValidationFlowStore
var viewStore: RecoveryPhraseValidationFlowViewStore {
ViewStore(store)
}
public init(store: RecoveryPhraseValidationFlowStore) {
self.store = store
}
public var body: some View {
VStack(alignment: .center) {
header(for: viewStore)
.padding(.horizontal)
.padding(.bottom, 10)
ZStack {
Asset.Colors.BackgroundColors.phraseGridDarkGray.color
.edgesIgnoringSafeArea(.bottom)
VStack(alignment: .center, spacing: 35) {
let state = viewStore.state
let groups = state.phrase.toGroups()
ForEach(Array(zip(groups.indices, groups)), id: \.0) { index, group in
WordChipGrid(
state: state,
groupIndex: index,
wordGroup: group,
misingIndex: index
)
.frame(alignment: .center)
.background(Asset.Colors.BackgroundColors.phraseGridDarkGray.color)
.whenIsDroppable(
!state.groupCompleted(index: index),
dropDelegate: WordChipDropDelegate { chipKind in
viewStore.send(.move(wordChip: chipKind, intoGroup: index))
}
)
}
Spacer()
}
.padding()
.padding(.top, 0)
.navigationLinkEmpty(
isActive: viewStore.bindingForSuccess,
destination: { RecoveryPhraseBackupSucceededView(store: store) }
)
.navigationLinkEmpty(
isActive: viewStore.bindingForFailure,
destination: { RecoveryPhraseBackupFailedView(store: store) }
)
}
.frame(alignment: .top)
}
.applyScreenBackground()
.scrollableWhenScaledUp()
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(Text(L10n.RecoveryPhraseBackupValidation.title))
}
}
private extension RecoveryPhraseBackupView {
@ViewBuilder func header(for viewStore: RecoveryPhraseValidationFlowViewStore) -> some View {
VStack {
if viewStore.isComplete {
completeHeader(for: viewStore.state)
} else {
Text(L10n.RecoveryPhraseBackupValidation.description)
.bodyText()
}
viewStore.state.missingWordGrid()
}
.padding(.horizontal, 30)
}
@ViewBuilder func completeHeader(for state: RecoveryPhraseValidationFlowReducer.State) -> some View {
if state.isValid {
Text(L10n.RecoveryPhraseBackupValidation.successResult)
.bodyText()
} else {
Text(L10n.RecoveryPhraseBackupValidation.failedResult)
.bodyText()
}
}
}
private extension RecoveryPhraseValidationFlowReducer.State {
@ViewBuilder func missingWordGrid() -> some View {
let columns = Array(
repeating: GridItem(.flexible(minimum: 100, maximum: 120), spacing: 20),
count: 2
)
LazyVGrid(columns: columns, alignment: .center, spacing: 20) {
ForEach(0..<missingWordChips.count, id: \.self) { chipIndex in
PhraseChip(kind: missingWordChips[chipIndex])
.makeDraggable()
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 30
)
}
}
.padding(0)
}
}
extension RecoveryPhraseValidationFlowReducer.State {
public func wordsChips(
for groupIndex: Int,
groupSize: Int,
from wordGroup: RecoveryPhrase.Group
) -> [PhraseChip.Kind] {
let validationWord = validationWords.first(where: { $0.groupIndex == groupIndex })
return wordGroup.words.enumerated().map { index, word in
guard index == missingIndices[groupIndex] else {
return .ordered(position: (groupSize * groupIndex) + index + 1, word: word)
}
if let completedWord = validationWord?.word {
return .unassigned(word: completedWord, color: self.coloredChipColor)
}
return .empty
}
}
}
private extension WordChipGrid {
init(
state: RecoveryPhraseValidationFlowReducer.State,
groupIndex: Int,
wordGroup: RecoveryPhrase.Group,
misingIndex: Int
) {
let chips = state.wordsChips(
for: groupIndex,
groupSize: RecoveryPhraseValidationFlowReducer.State.wordGroupSize,
from: wordGroup
)
self.init(chips: chips, coloredChipColor: state.coloredChipColor)
}
}
private extension RecoveryPhraseValidationFlowReducer.State {
var coloredChipColor: Color {
if self.isComplete {
return isValid ? Asset.Colors.Buttons.activeButton.color : Asset.Colors.BackgroundColors.red.color
} else {
return Asset.Colors.Buttons.activeButton.color
}
}
}
// MARK: - Previews
struct RecoveryPhraseBackupView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
RecoveryPhraseValidationFlowView(store: .demoStep4)
}
NavigationView {
RecoveryPhraseValidationFlowView(store: .demoStep1)
}
NavigationView {
RecoveryPhraseValidationFlowView(store: .demoStep1)
}
}
}

View File

@ -19,7 +19,6 @@ extension RootReducer {
case home
case onboarding
case phraseDisplay
case phraseValidation
case sandbox
case startup
case welcome
@ -55,25 +54,8 @@ extension RootReducer {
case .sandbox(.reset):
state.destinationState.destination = .startup
case .phraseValidation(.proceedToHome):
state.destinationState.destination = .home
case .phraseValidation(.displayBackedUpPhrase):
state.destinationState.destination = .phraseDisplay
case .phraseDisplay(.finishedPressed):
// user is still supposed to do the backup phrase validation test
if (state.destinationState.previousDestination == .welcome
|| state.destinationState.previousDestination == .onboarding
|| state.destinationState.previousDestination == .startup)
&& state.walletConfig.isEnabled(.testBackupPhraseFlow) {
state.destinationState.destination = .phraseValidation
}
// user wanted to see the backup phrase once again (at validation finished screen)
if state.destinationState.previousDestination == .phraseValidation
|| !state.walletConfig.isEnabled(.testBackupPhraseFlow) {
state.destinationState.destination = .home
}
state.destinationState.destination = .home
case .destination(.deeplink(let url)):
// get the latest synchronizer state
@ -122,7 +104,7 @@ extension RootReducer {
}
return EffectTask(value: .destination(.deeplink(url)))
case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .updateStateAfterConfigUpdate, .alert,
case .home, .initialization, .onboarding, .phraseDisplay, .sandbox, .updateStateAfterConfigUpdate, .alert,
.welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .exportLogs:
return .none
}

View File

@ -175,7 +175,6 @@ extension RootReducer {
let recoveryPhrase = RecoveryPhrase(words: phraseWords.map { $0.redacted })
state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = randomRecoveryPhrase.random(recoveryPhrase)
landingDestination = .phraseDisplay
}
@ -199,25 +198,16 @@ extension RootReducer {
let randomRecoveryPhraseWords = mnemonic.asWords(newRandomPhrase)
let recoveryPhrase = RecoveryPhrase(words: randomRecoveryPhraseWords.map { $0.redacted })
state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = randomRecoveryPhrase.random(recoveryPhrase)
return .concatenate(
EffectTask(value: .initialization(.initializeSDK(.newWallet))),
EffectTask(value: .phraseValidation(.displayBackedUpPhrase))
EffectTask(value: .destination(.updateDestination(.phraseDisplay)))
)
} catch {
state.alert = AlertState.cantCreateNewWallet(error.toZcashError())
}
return .none
case .phraseValidation(.succeed):
do {
try walletStorage.markUserPassedPhraseBackupTest(true)
} catch {
state.alert = AlertState.cantStoreThatUserPassedPhraseBackupTest(error.toZcashError())
}
return .none
case .initialization(.nukeWalletRequest):
state.alert = AlertState.wipeRequest()
return .none
@ -237,7 +227,6 @@ extension RootReducer {
case .nukeWalletSucceeded:
walletStorage.nukeWallet()
state.onboardingState.destination = nil
state.onboardingState.index = 0
return .concatenate(
.cancel(id: SynchronizerCancelId.timer),
EffectTask(value: .initialization(.checkWalletInitialization))
@ -288,7 +277,7 @@ extension RootReducer {
state.alert = AlertState.initializationFailed(error)
return .none
case .home, .destination, .onboarding, .phraseDisplay, .phraseValidation, .sandbox,
case .home, .destination, .onboarding, .phraseDisplay, .sandbox,
.welcome, .binding, .debug, .exportLogs, .alert:
return .none
}

View File

@ -2,7 +2,6 @@ import ComposableArchitecture
import ZcashLightClientKit
import DatabaseFiles
import Deeplink
import RecoveryPhraseValidationFlow
import ZcashSDKEnvironment
import WalletStorage
import WalletConfigProvider
@ -36,7 +35,6 @@ public struct RootReducer: ReducerProtocol {
public var exportLogsState: ExportLogsReducer.State
public var homeState: HomeReducer.State
public var onboardingState: OnboardingFlowReducer.State
public var phraseValidationState: RecoveryPhraseValidationFlowReducer.State
public var phraseDisplayState: RecoveryPhraseDisplayReducer.State
public var sandboxState: SandboxReducer.State
public var storedWallet: StoredWallet?
@ -50,7 +48,6 @@ public struct RootReducer: ReducerProtocol {
exportLogsState: ExportLogsReducer.State,
homeState: HomeReducer.State,
onboardingState: OnboardingFlowReducer.State,
phraseValidationState: RecoveryPhraseValidationFlowReducer.State,
phraseDisplayState: RecoveryPhraseDisplayReducer.State,
sandboxState: SandboxReducer.State,
storedWallet: StoredWallet? = nil,
@ -63,7 +60,6 @@ public struct RootReducer: ReducerProtocol {
self.exportLogsState = exportLogsState
self.homeState = homeState
self.onboardingState = onboardingState
self.phraseValidationState = phraseValidationState
self.phraseDisplayState = phraseDisplayState
self.sandboxState = sandboxState
self.storedWallet = storedWallet
@ -84,7 +80,6 @@ public struct RootReducer: ReducerProtocol {
case nukeWalletSucceeded
case onboarding(OnboardingFlowReducer.Action)
case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
case phraseValidation(RecoveryPhraseValidationFlowReducer.Action)
case sandbox(SandboxReducer.Action)
case updateStateAfterConfigUpdate(WalletConfig)
case walletConfigLoaded(WalletConfig)
@ -97,7 +92,6 @@ public struct RootReducer: ReducerProtocol {
@Dependency(\.derivationTool) var derivationTool
@Dependency(\.mainQueue) var mainQueue
@Dependency(\.mnemonic) var mnemonic
@Dependency(\.randomRecoveryPhrase) var randomRecoveryPhrase
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.userStoredPreferences) var userStoredPreferences
@Dependency(\.walletConfigProvider) var walletConfigProvider
@ -123,10 +117,6 @@ public struct RootReducer: ReducerProtocol {
OnboardingFlowReducer(saplingActivationHeight: zcashNetwork.constants.saplingActivationHeight)
}
Scope(state: \.phraseValidationState, action: /Action.phraseValidation) {
RecoveryPhraseValidationFlowReducer()
}
Scope(state: \.phraseDisplayState, action: /Action.phraseDisplay) {
RecoveryPhraseDisplayReducer()
}
@ -336,7 +326,6 @@ extension RootReducer.State {
walletConfig: .default,
importWalletState: .placeholder
),
phraseValidationState: .placeholder,
phraseDisplayState: RecoveryPhraseDisplayReducer.State(
phrase: .placeholder
),

View File

@ -2,7 +2,6 @@ import SwiftUI
import StoreKit
import ComposableArchitecture
import Generated
import RecoveryPhraseValidationFlow
import Models
import RecoveryPhraseDisplay
import Welcome
@ -74,22 +73,12 @@ private extension RootView {
case .onboarding:
NavigationView {
if viewStore.walletConfig
.isEnabled(.onboardingFlow) {
OnboardingScreen(
store: store.scope(
state: \.onboardingState,
action: RootReducer.Action.onboarding
)
PlainOnboardingView(
store: store.scope(
state: \.onboardingState,
action: RootReducer.Action.onboarding
)
} else {
PlainOnboardingView(
store: store.scope(
state: \.onboardingState,
action: RootReducer.Action.onboarding
)
)
}
)
}
.navigationViewStyle(.stack)
@ -98,18 +87,7 @@ private extension RootView {
debugView(viewStore)
.transition(.opacity)
}
case .phraseValidation:
NavigationView {
RecoveryPhraseValidationFlowView(
store: store.scope(
state: \.phraseValidationState,
action: RootReducer.Action.phraseValidation
)
)
}
.navigationViewStyle(.stack)
case .phraseDisplay:
NavigationView {
RecoveryPhraseDisplayView(
@ -163,10 +141,10 @@ private extension RootView {
@ViewBuilder func debugView(_ viewStore: RootViewStore) -> some View {
VStack(alignment: .leading) {
if viewStore.destinationState.previousDestination == .home {
Button(L10n.General.back) {
Button(L10n.General.back.uppercased()) {
viewStore.goToDestination(.home)
}
.activeButtonStyle
.zcashStyle()
.frame(width: 150)
.padding()
}

View File

@ -74,7 +74,7 @@ extension ScanView {
presentationMode.wrappedValue.dismiss()
}, label: {
Image(systemName: "arrow.backward")
.foregroundColor(Asset.Colors.Mfp.background.color)
.foregroundColor(Asset.Colors.primary.color)
.font(
.custom(FontFamily.Inter.regular.name, size: 30)
)
@ -101,7 +101,7 @@ extension ScanView {
Image(
systemName: viewStore.isTorchOn ? "lightbulb.fill" : "lightbulb.slash"
)
.foregroundColor(Asset.Colors.Mfp.background.color)
.foregroundColor(Asset.Colors.primary.color)
.font(
.custom(FontFamily.Inter.regular.name, size: 30)
)
@ -118,7 +118,7 @@ extension ScanView {
func frameOfInterest(_ size: CGSize) -> some View {
Rectangle()
.stroke(Asset.Colors.Mfp.background.color, lineWidth: 5.0)
.stroke(Asset.Colors.primary.color, lineWidth: 5.0)
.frame(
width: frameSize(size),
height: frameSize(size),

View File

@ -31,7 +31,7 @@ public struct CreateTransaction: View {
.custom(FontFamily.Inter.regular.name, size: 14)
)
}
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
.padding(.horizontal)
VStack(alignment: .leading) {
@ -44,7 +44,7 @@ public struct CreateTransaction: View {
if viewStore.isInvalidAddressFormat {
Text(L10n.Send.Error.invalidAddress)
.foregroundColor(Asset.Colors.Mfp.error.color)
.foregroundColor(Asset.Colors.error.color)
}
}
.padding(.horizontal)
@ -61,10 +61,10 @@ public struct CreateTransaction: View {
if viewStore.isInvalidAmountFormat {
Text(L10n.Send.Error.invalidAmount)
.foregroundColor(Asset.Colors.Mfp.error.color)
.foregroundColor(Asset.Colors.error.color)
} else if viewStore.isInsufficientFunds {
Text(L10n.Send.Error.insufficientFunds)
.foregroundColor(Asset.Colors.Mfp.error.color)
.foregroundColor(Asset.Colors.error.color)
}
}
.padding(.horizontal)
@ -77,19 +77,19 @@ public struct CreateTransaction: View {
L10n.Send.editMemo
: L10n.Send.includeMemo
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
}
.padding(.top, 10)
.disable(when: !viewStore.isMemoInputEnabled, dimmingOpacity: 0.5)
.disabled(!viewStore.isMemoInputEnabled)
Button(
action: { viewStore.send(.sendPressed) },
label: { Text(L10n.General.send) }
label: { Text(L10n.General.send.uppercased()) }
)
.activeButtonStyle
.disable(when: !viewStore.isValidForm, dimmingOpacity: 0.5)
.zcashStyle()
.disabled(!viewStore.isValidForm)
.padding(.top, 10)
.padding(.horizontal)
.padding(.horizontal, 70)
Spacer()
}

View File

@ -18,10 +18,11 @@ public struct TransactionFailed: View {
action: {
viewStore.send(.updateDestination(.done))
},
label: { Text(L10n.General.close) }
label: { Text(L10n.General.close.uppercased()) }
)
.activeButtonStyle
.zcashStyle()
.frame(height: 50)
.padding(.horizontal, 70)
.padding()
Spacer()

View File

@ -25,11 +25,11 @@ public struct TransactionSendingView: View {
VStack(alignment: .center, spacing: 40) {
Spacer()
Text(L10n.Send.sendingTo(viewStore.amount.decimalString(), tokenName))
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
Text(viewStore.address)
.truncationMode(.middle)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
.lineLimit(1)
Spacer()

View File

@ -18,10 +18,11 @@ public struct TransactionSent: View {
action: {
viewStore.send(.updateDestination(.done))
},
label: { Text(L10n.General.close) }
label: { Text(L10n.General.close.uppercased()) }
)
.activeButtonStyle
.zcashStyle()
.frame(height: 50)
.padding(.horizontal, 70)
.padding()
Text(L10n.Send.amount(viewStore.amount.decimalString()))

View File

@ -19,48 +19,46 @@ public struct SettingsView: View {
L10n.Settings.crashReporting,
isOn: viewStore.binding(\.$isCrashReportingOn)
)
Button(
action: { viewStore.send(.backupWalletAccessRequest) },
label: { Text(L10n.Settings.backupWallet) }
label: { Text(L10n.Settings.backupWallet.uppercased()) }
)
.activeButtonStyle
.frame(height: 50)
.zcashStyle()
.padding(.horizontal, 70)
Button(
action: { viewStore.send(.exportLogs(.start)) },
label: {
if viewStore.exportLogsState.exportLogsDisabled {
HStack {
ProgressView()
Text(L10n.Settings.exporting)
Text(L10n.Settings.exporting.uppercased())
}
} else {
Text(L10n.Settings.exportLogs)
Text(L10n.Settings.exportLogs.uppercased())
}
}
)
.activeButtonStyle
.frame(height: 50)
.disable(
when: viewStore.exportLogsState.exportLogsDisabled,
dimmingOpacity: 0.5
)
.zcashStyle()
.padding(.horizontal, 70)
.disabled(viewStore.exportLogsState.exportLogsDisabled)
Button(
action: { viewStore.send(.sendSupportMail) },
label: { Text(L10n.Settings.feedback) }
label: { Text(L10n.Settings.feedback.uppercased()) }
)
.activeButtonStyle
.frame(height: 50)
.zcashStyle()
.padding(.horizontal, 70)
Spacer()
Button(
action: { viewStore.send(.updateDestination(.about)) },
label: { Text(L10n.Settings.about) }
label: { Text(L10n.Settings.about.uppercased()) }
)
.activeButtonStyle
.frame(maxHeight: 50)
.zcashStyle()
.padding(.horizontal, 70)
.padding(.bottom, 50)
}
.padding(.horizontal, 30)

View File

@ -20,7 +20,7 @@ public struct About: View {
WithViewStore(store) { viewStore in
VStack {
Text(L10n.Settings.version(viewStore.appVersion, viewStore.appBuild))
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
Spacer()
}

View File

@ -175,11 +175,11 @@ struct TransactionDetailRow: ViewModifier {
let markColor: Color
switch mark {
case .neutral: markColor = Asset.Colors.TransactionDetail.neutralMark.color
case .success: markColor = Asset.Colors.TransactionDetail.succeededMark.color
case .fail: markColor = Asset.Colors.TransactionDetail.failedMark.color
case .inactive: markColor = Asset.Colors.TransactionDetail.inactiveMark.color
case .highlight: markColor = Asset.Colors.TransactionDetail.highlightMark.color
case .neutral: markColor = Asset.Colors.primary.color
case .success: markColor = Asset.Colors.primary.color
case .fail: markColor = Asset.Colors.primary.color
case .inactive: markColor = Asset.Colors.primary.color
case .highlight: markColor = Asset.Colors.primary.color
}
return markColor
@ -194,9 +194,9 @@ extension View {
TransactionDetailRow(
mark: mark,
textColor: mark == .inactive ?
Asset.Colors.TransactionDetail.inactiveMark.color :
Asset.Colors.Text.transactionDetailText.color,
backgroundColor: Asset.Colors.BackgroundColors.numberedChip.color
Asset.Colors.primary.color :
Asset.Colors.primary.color,
backgroundColor: Asset.Colors.primary.color
)
)
}

View File

@ -29,13 +29,13 @@ public struct TransactionRowView: View {
.font(
.custom(FontFamily.Inter.regular.name, size: 16)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
Text("\(transaction.date?.asHumanReadable() ?? L10n.General.dateNotAvailable)")
.font(
.custom(FontFamily.Inter.regular.name, size: 16)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
.opacity(0.5)
}
@ -46,12 +46,12 @@ public struct TransactionRowView: View {
.font(
.custom(FontFamily.Inter.regular.name, size: 16)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
+ Text(L10n.balance(transaction.zecAmount.decimalString(), tokenName))
.font(
.custom(FontFamily.Inter.regular.name, size: 16)
)
.foregroundColor(Asset.Colors.Mfp.fontDark.color)
.foregroundColor(Asset.Colors.primary.color)
}
.padding(.trailing, 30)
}
@ -62,7 +62,7 @@ public struct TransactionRowView: View {
Rectangle()
.padding(.horizontal, 30)
.frame(height: 1, alignment: .center)
.foregroundColor(Asset.Colors.Text.transactionRowSubtitle.color)
.foregroundColor(Asset.Colors.primary.color)
}
}
.frame(height: 60)
@ -96,7 +96,7 @@ extension TransactionRowView {
.frame(width: 12, height: 12)
.foregroundColor(inTransaction ? .yellow : .white)
.padding(10)
.background(Asset.Colors.Mfp.walletEvents.color)
.background(Asset.Colors.suppressed72.color)
.cornerRadius(40)
.rotationEffect(Angle(degrees: inTransaction ? 135 : -45))
.padding(.leading, 14)

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,23 +0,0 @@
{
"images" : [
{
"filename" : "callout0.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "callout0-1.jpg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "callout0-2.jpg",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "ua_welcome_lighttheme.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "ua_welcome_darktheme.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "illus_ua_lighttheme.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "illus_ua_darktheme.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "illus_future_lighttheme.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "illus_future_darktheme.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "illus_done_lighttheme.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "illus_done_darktheme.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "illus_alert_lighttheme.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "illus_alert_darktheme.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "illus_bank_lighttheme.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "illus_bank_darktheme.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "illus_bigsuccess_lighttheme.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "illus_bigsuccess_darktheme.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFB",
"green" : "0xF5",
"red" : "0xEE"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x62",
"green" : "0x3D",
"red" : "0x30"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xE9",
"green" : "0xE1",
"red" : "0xD8"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x3A",
"green" : "0x36",
"red" : "0x31"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x2C",
"green" : "0x0B",
"red" : "0xC6"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "251",
"green" : "245",
"red" : "238"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF9",
"green" : "0xEF",
"red" : "0xE3"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x5A",
"green" : "0x36",
"red" : "0x29"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.341",
"green" : "0.200",
"red" : "0.149"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.725",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.341",
"green" : "0.200",
"red" : "0.149"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.200",
"blue" : "0x28",
"green" : "0xB7",
"red" : "0xF4"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x57",
"green" : "0x33",
"red" : "0x26"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.847",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.330",
"blue" : "0x36",
"green" : "0x2C",
"red" : "0x27"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.000",
"blue" : "0x36",
"green" : "0x2C",
"red" : "0x27"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.820",
"green" : "0.722",
"red" : "0.631"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.150",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.992",
"green" : "0.980",
"red" : "0.969"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.150",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFD",
"green" : "0xF7",
"red" : "0xF1"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "217",
"green" : "192",
"red" : "167"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFC",
"green" : "0xF8",
"red" : "0xF5"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xEF",
"green" : "0xDC",
"red" : "0xC8"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFD",
"green" : "0xF7",
"red" : "0xF1"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "217",
"green" : "192",
"red" : "167"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.976",
"green" : "0.941",
"red" : "0.902"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.200",
"blue" : "0xEF",
"green" : "0xDC",
"red" : "0xC8"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "253",
"green" : "250",
"red" : "244"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.937",
"green" : "0.863",
"red" : "0.784"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "246",
"green" : "234",
"red" : "222"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "106",
"green" : "72",
"red" : "61"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x65",
"green" : "0x65",
"red" : "0x65"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x65",
"green" : "0x65",
"red" : "0x65"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x94",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x94",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.171",
"green" : "0.138",
"red" : "0.126"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0xCE",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.008",
"green" : "0.000",
"red" : "0.806"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.008",
"green" : "0.000",
"red" : "0.806"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.235",
"green" : "0.235",
"red" : "0.235"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.235",
"green" : "0.235",
"red" : "0.235"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x3C",
"green" : "0x3C",
"red" : "0x3C"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.235",
"green" : "0.235",
"red" : "0.235"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.235",
"green" : "0.235",
"red" : "0.235"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF7",
"green" : "0xED",
"red" : "0xE3"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "69",
"green" : "32",
"red" : "19"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xEF",
"green" : "0xE6",
"red" : "0xDB"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x46",
"green" : "0x25",
"red" : "0x19"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF9",
"green" : "0xF2",
"red" : "0xEF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x73",
"green" : "0x4F",
"red" : "0x42"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFA",
"green" : "0xF0",
"red" : "0xE4"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x82",
"green" : "0x51",
"red" : "0x3D"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFA",
"green" : "0xF0",
"red" : "0xE4"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x82",
"green" : "0x51",
"red" : "0x3D"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,38 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF7",
"green" : "0xED",
"red" : "0xE3"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xD9",
"green" : "0xC0",
"red" : "0xA7"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Some files were not shown because too many files have changed in this diff Show More