Merge pull request #113 from adamstener/onboarding
Onboarding: Skip Functionality
This commit is contained in:
commit
3068e6a168
|
@ -11,38 +11,54 @@ import ComposableArchitecture
|
||||||
|
|
||||||
struct OnboardingStep: Equatable, Identifiable {
|
struct OnboardingStep: Equatable, Identifiable {
|
||||||
let id: UUID
|
let id: UUID
|
||||||
|
let title: String
|
||||||
let description: String
|
let description: String
|
||||||
let imageName: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OnboardingState: Equatable {
|
struct OnboardingState: Equatable {
|
||||||
var steps: IdentifiedArrayOf<OnboardingStep> = Self.onboardingSteps
|
var steps: IdentifiedArrayOf<OnboardingStep> = Self.onboardingSteps
|
||||||
var index = 0
|
var index = 0
|
||||||
var offset: CGFloat = .zero
|
var offset: CGFloat = .zero
|
||||||
|
var skippedAtindex: Int?
|
||||||
|
|
||||||
var currentStep: OnboardingStep { steps[index] }
|
var currentStep: OnboardingStep { steps[index] }
|
||||||
var nextButtonDisabled: Bool { steps.count == index + 1 }
|
var skipButtonDisabled: Bool { steps.count == index + 1 }
|
||||||
var backButtonDisabled: Bool { index == 0 }
|
var backButtonDisabled: Bool { index == 0 }
|
||||||
var progress: Int {
|
var progress: Int { ((index + 1) * 100) / (steps.count) }
|
||||||
((index + 1) * 100) / (steps.count)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OnboardingAction: Equatable {
|
enum OnboardingAction: Equatable {
|
||||||
case nextPressed
|
case next
|
||||||
case backPressed
|
case back
|
||||||
|
case skip
|
||||||
|
case createNewWallet
|
||||||
}
|
}
|
||||||
|
|
||||||
let onboardingReducer = Reducer<OnboardingState, OnboardingAction, Void> { state, action, _ in
|
let onboardingReducer = Reducer<OnboardingState, OnboardingAction, Void> { state, action, _ in
|
||||||
switch action {
|
switch action {
|
||||||
case .backPressed:
|
case .back:
|
||||||
state.index -= 1
|
guard state.index > 0 else { return .none }
|
||||||
state.offset += 20.0
|
if let skippedFrom = state.skippedAtindex {
|
||||||
|
state.index = skippedFrom
|
||||||
|
state.skippedAtindex = nil
|
||||||
|
} else {
|
||||||
|
state.index -= 1
|
||||||
|
state.offset += 20.0
|
||||||
|
}
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
case .nextPressed:
|
case .next:
|
||||||
|
guard state.index < state.steps.count - 1 else { return .none }
|
||||||
state.index += 1
|
state.index += 1
|
||||||
state.offset -= 20.0
|
state.offset -= 20.0
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
|
case .skip:
|
||||||
|
state.skippedAtindex = state.index
|
||||||
|
state.index = state.steps.count - 1
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .createNewWallet:
|
||||||
|
return .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,26 +15,20 @@ struct OnboardingView: View {
|
||||||
WithViewStore(self.store) { viewStore in
|
WithViewStore(self.store) { viewStore in
|
||||||
VStack(spacing: 50) {
|
VStack(spacing: 50) {
|
||||||
HStack(spacing: 50) {
|
HStack(spacing: 50) {
|
||||||
Button(
|
Button("Back") { viewStore.send(.back) }
|
||||||
action: { viewStore.send(.backPressed) },
|
.disabled(viewStore.backButtonDisabled)
|
||||||
label: { Text("Previous") }
|
|
||||||
)
|
|
||||||
.disabled(viewStore.backButtonDisabled)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button(
|
Button("Skip") { viewStore.send(.skip) }
|
||||||
action: { viewStore.send(.nextPressed) },
|
.disabled(viewStore.skipButtonDisabled)
|
||||||
label: { Text("Next") }
|
|
||||||
)
|
|
||||||
.disabled(viewStore.nextButtonDisabled)
|
|
||||||
}
|
}
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
.padding(.horizontal, 50)
|
.padding(.horizontal, 50)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(viewStore.currentStep.imageName)
|
Text(viewStore.currentStep.title)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.offset(y: viewStore.offset)
|
.offset(y: viewStore.offset)
|
||||||
.animation(.easeOut(duration: 0.4))
|
.animation(.easeOut(duration: 0.4))
|
||||||
|
@ -58,23 +52,29 @@ struct OnboardingView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable line_length
|
||||||
extension OnboardingState {
|
extension OnboardingState {
|
||||||
static let onboardingSteps = IdentifiedArray(
|
static let onboardingSteps = IdentifiedArray(
|
||||||
uniqueElements: [
|
uniqueElements: [
|
||||||
OnboardingStep(
|
OnboardingStep(
|
||||||
id: UUID(),
|
id: UUID(),
|
||||||
description: "This is the description of the first onboarding step, please read it carefully.",
|
title: "Shielded by Default",
|
||||||
imageName: "Image"
|
description: "Tired of worrying about which wallet you used last? US TOO! Now you don't have to, as all funds will automatically be moved to your shielded wallet (and migrated for you)."
|
||||||
),
|
),
|
||||||
OnboardingStep(
|
OnboardingStep(
|
||||||
id: UUID(),
|
id: UUID(),
|
||||||
description: "The second step is even more important, have to pay attention to the details here.",
|
title: "Unified Addresses",
|
||||||
imageName: "Image"
|
description: "Tired of worrying about which wallet you used last? US TOO! Now you don't have to, as all funds will automatically be moved to your shielded wallet (and migrated for you)."
|
||||||
),
|
),
|
||||||
OnboardingStep(
|
OnboardingStep(
|
||||||
id: UUID(),
|
id: UUID(),
|
||||||
description: "Congratulations you made it all the way through to the end, you can use the app now!",
|
title: "And so much more...",
|
||||||
imageName: "Image"
|
description: "Faster reverse syncing (yes it's a thing). Liberated Payments, Social Payments, Address Books, in-line ZEC requests, wrapped Bitcoin, fractionalize NFTs, you providing liquidity for anything you want, getting that Defi, and going to Mexico."
|
||||||
|
),
|
||||||
|
OnboardingStep(
|
||||||
|
id: UUID(),
|
||||||
|
title: "Ready for the Future",
|
||||||
|
description: "Lets get you set up!"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,23 +17,33 @@ class OnboardingStoreTests: XCTestCase {
|
||||||
environment: ()
|
environment: ()
|
||||||
)
|
)
|
||||||
|
|
||||||
store.send(.nextPressed) {
|
store.send(.next) {
|
||||||
$0.index += 1
|
$0.index += 1
|
||||||
$0.offset -= 20.0
|
$0.offset -= 20.0
|
||||||
|
|
||||||
XCTAssertFalse($0.nextButtonDisabled)
|
XCTAssertFalse($0.skipButtonDisabled)
|
||||||
XCTAssertFalse($0.backButtonDisabled)
|
XCTAssertFalse($0.backButtonDisabled)
|
||||||
XCTAssertEqual($0.currentStep, $0.steps[1])
|
XCTAssertEqual($0.currentStep, $0.steps[1])
|
||||||
XCTAssertEqual($0.progress, 66)
|
XCTAssertEqual($0.progress, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.send(.nextPressed) {
|
store.send(.next) {
|
||||||
$0.index += 1
|
$0.index += 1
|
||||||
$0.offset -= 20.0
|
$0.offset -= 20.0
|
||||||
|
|
||||||
XCTAssertTrue($0.nextButtonDisabled)
|
XCTAssertFalse($0.skipButtonDisabled)
|
||||||
XCTAssertFalse($0.backButtonDisabled)
|
XCTAssertFalse($0.backButtonDisabled)
|
||||||
XCTAssertEqual($0.currentStep, $0.steps[2])
|
XCTAssertEqual($0.currentStep, $0.steps[2])
|
||||||
|
XCTAssertEqual($0.progress, 75)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.send(.next) {
|
||||||
|
$0.index += 1
|
||||||
|
$0.offset -= 20.0
|
||||||
|
|
||||||
|
XCTAssertTrue($0.skipButtonDisabled)
|
||||||
|
XCTAssertFalse($0.backButtonDisabled)
|
||||||
|
XCTAssertEqual($0.currentStep, $0.steps[3])
|
||||||
XCTAssertEqual($0.progress, 100)
|
XCTAssertEqual($0.progress, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,24 +58,60 @@ class OnboardingStoreTests: XCTestCase {
|
||||||
environment: ()
|
environment: ()
|
||||||
)
|
)
|
||||||
|
|
||||||
store.send(.backPressed) {
|
store.send(.back) {
|
||||||
$0.index -= 1
|
$0.index -= 1
|
||||||
$0.offset += 20.0
|
$0.offset += 20.0
|
||||||
|
|
||||||
XCTAssertFalse($0.nextButtonDisabled)
|
XCTAssertFalse($0.skipButtonDisabled)
|
||||||
XCTAssertFalse($0.backButtonDisabled)
|
XCTAssertFalse($0.backButtonDisabled)
|
||||||
XCTAssertEqual($0.currentStep, $0.steps[1])
|
XCTAssertEqual($0.currentStep, $0.steps[1])
|
||||||
XCTAssertEqual($0.progress, 66)
|
XCTAssertEqual($0.progress, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.send(.backPressed) {
|
store.send(.back) {
|
||||||
$0.index -= 1
|
$0.index -= 1
|
||||||
$0.offset += 20.0
|
$0.offset += 20.0
|
||||||
|
|
||||||
XCTAssertFalse($0.nextButtonDisabled)
|
XCTAssertFalse($0.skipButtonDisabled)
|
||||||
XCTAssertTrue($0.backButtonDisabled)
|
XCTAssertTrue($0.backButtonDisabled)
|
||||||
XCTAssertEqual($0.currentStep, $0.steps[0])
|
XCTAssertEqual($0.currentStep, $0.steps[0])
|
||||||
XCTAssertEqual($0.progress, 33)
|
XCTAssertEqual($0.progress, 25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSkipOnboarding() {
|
||||||
|
let initialIndex = 1
|
||||||
|
let initialOffset: CGFloat = .zero - 20.0
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: OnboardingState(
|
||||||
|
index: initialIndex,
|
||||||
|
offset: initialOffset
|
||||||
|
),
|
||||||
|
reducer: onboardingReducer,
|
||||||
|
environment: ()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.skip) {
|
||||||
|
$0.index = $0.steps.count - 1
|
||||||
|
$0.offset = initialOffset
|
||||||
|
$0.skippedAtindex = initialIndex
|
||||||
|
|
||||||
|
XCTAssertTrue($0.skipButtonDisabled)
|
||||||
|
XCTAssertFalse($0.backButtonDisabled)
|
||||||
|
XCTAssertEqual($0.currentStep, $0.steps[3])
|
||||||
|
XCTAssertEqual($0.progress, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.send(.back) {
|
||||||
|
$0.skippedAtindex = nil
|
||||||
|
$0.index = initialIndex
|
||||||
|
$0.offset = initialOffset
|
||||||
|
|
||||||
|
XCTAssertFalse($0.skipButtonDisabled)
|
||||||
|
XCTAssertFalse($0.backButtonDisabled)
|
||||||
|
XCTAssertEqual($0.currentStep, $0.steps[1])
|
||||||
|
XCTAssertEqual($0.progress, 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue