Skip to content

Commit

Permalink
Merge pull request #1666 from planetary-social/onboarding-success
Browse files Browse the repository at this point in the history
#1599: Account Success screen in onboarding
  • Loading branch information
joshuatbrown authored Oct 18, 2024
2 parents 6905591 + 5be391d commit 8f7b510
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the Public Key onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1596](https://github.com/planetary-social/nos/issues/1596)
- Added the Display Name onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1597](https://github.com/planetary-social/nos/issues/1597)
- Added the Username onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1598](https://github.com/planetary-social/nos/issues/1598)
- Added the Account Success onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1599](https://github.com/planetary-social/nos/issues/1599)

## [0.2.2] - 2024-10-11Z

Expand Down
4 changes: 4 additions & 0 deletions Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
039C961F2C480F4100A8EB39 /* unsupported_kinds.json in Resources */ = {isa = PBXBuildFile; fileRef = 039C961E2C480F4100A8EB39 /* unsupported_kinds.json */; };
039C96292C48321E00A8EB39 /* long_form_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 039C96282C48321E00A8EB39 /* long_form_data.json */; };
039F09592CC051FF00FEEC81 /* CreateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039F09582CC051EE00FEEC81 /* CreateAccountView.swift */; };
03A241BD2CC2F458007EA31B /* AccountSuccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A241BC2CC2F458007EA31B /* AccountSuccessView.swift */; };
03A3AA3B2C5028FF008FE153 /* PublicKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A3AA3A2C5028FF008FE153 /* PublicKeyTests.swift */; };
03A743452CC048C700893CAE /* GoToFeedTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A743442CC048C700893CAE /* GoToFeedTip.swift */; };
03B4E6A22C125CA1006E5F59 /* nostr_build_nip96_upload_response.json in Resources */ = {isa = PBXBuildFile; fileRef = 03B4E6A12C125CA1006E5F59 /* nostr_build_nip96_upload_response.json */; };
Expand Down Expand Up @@ -658,6 +659,7 @@
039C961E2C480F4100A8EB39 /* unsupported_kinds.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = unsupported_kinds.json; sourceTree = "<group>"; };
039C96282C48321E00A8EB39 /* long_form_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = long_form_data.json; sourceTree = "<group>"; };
039F09582CC051EE00FEEC81 /* CreateAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountView.swift; sourceTree = "<group>"; };
03A241BC2CC2F458007EA31B /* AccountSuccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSuccessView.swift; sourceTree = "<group>"; };
03A3AA3A2C5028FF008FE153 /* PublicKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeyTests.swift; sourceTree = "<group>"; };
03A743442CC048C700893CAE /* GoToFeedTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoToFeedTip.swift; sourceTree = "<group>"; };
03AB2F7D2BF6609500B73DB1 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1482,6 +1484,7 @@
3FB5E64F299D288E00386527 /* Onboarding */ = {
isa = PBXGroup;
children = (
03A241BC2CC2F458007EA31B /* AccountSuccessView.swift */,
030FECAA2CB5E0B900820014 /* BuildYourNetworkView.swift */,
039F09582CC051EE00FEEC81 /* CreateAccountView.swift */,
030E570C2CC2A05B00A4A51E /* DisplayNameView.swift */,
Expand Down Expand Up @@ -2495,6 +2498,7 @@
5BCA95D22C8A5F0D00A52D1A /* PreviewEventRepository.swift in Sources */,
C95D68A6299E6F9E00429F86 /* ProfileHeader.swift in Sources */,
0365CD872C4016A200622A1A /* EventKind.swift in Sources */,
03A241BD2CC2F458007EA31B /* AccountSuccessView.swift in Sources */,
C9BAB09B2996FBA10003A84E /* EventProcessor.swift in Sources */,
C9B5C78E2C24AF650070445B /* MockRelaySubscriptionManager.swift in Sources */,
C960C57129F3236200929990 /* LikeButton.swift in Sources */,
Expand Down
48 changes: 48 additions & 0 deletions Nos/Assets/Localization/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,30 @@
}
}
},
"accountPartialSuccessDescription" : {
"comment" : "description for the account success screen when something isn't quite right",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "The app couldn't save your chosen Display Name or Username. You can set these up later in your Profile."
}
}
}
},
"accountPartialSuccessHeadline" : {
"comment" : "headline for the account success screen when something isn't quite right",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "You've finished setting up your account, but..."
}
}
}
},
"accountsIFollow" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -654,6 +678,30 @@
}
}
},
"accountSuccessDescription" : {
"comment" : "description for the account success screen when everything is right",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Now that you know who you are on Nostr, let’s find other people to follow!"
}
}
}
},
"accountSuccessHeadline" : {
"comment" : "headline for the account success screen when everything is right",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "You've finished setting up your account!"
}
}
}
},
"activity" : {
"extractionState" : "manual",
"localizations" : {
Expand Down
150 changes: 150 additions & 0 deletions Nos/Views/Onboarding/AccountSuccessView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import Dependencies
import SwiftUI

/// The Account Success view in the onboarding.
struct AccountSuccessView: View {
@Environment(OnboardingState.self) private var state
@Environment(CurrentUser.self) var currentUser

@Dependency(\.crashReporting) private var crashReporting

var body: some View {
ZStack {
Color.appBg
.ignoresSafeArea()
ViewThatFits(in: .vertical) {
accountSuccessStack

ScrollView {
accountSuccessStack
}
}
}
.navigationBarHidden(true)
}

var accountSuccessStack: some View {
VStack(alignment: .leading, spacing: 20) {
Text(emoji)
.font(.system(size: 60))
Text(headline)
.font(.clarityBold(.title))
.foregroundStyle(Color.primaryTxt)
.padding(.bottom, 10)
CompletedStepsView(state)
.padding(.horizontal, 10)
Spacer()
Text(description)
.foregroundStyle(Color.secondaryTxt)
BigActionButton("next") {
state.step = .buildYourNetwork
}
}
.padding(40)
.readabilityPadding()
}

var emoji: String {
state.allStepsSucceeded ? "🎉" : "🤔"
}

var headline: LocalizedStringKey {
state.allStepsSucceeded ? "accountSuccessHeadline" : "accountPartialSuccessHeadline"
}

var description: LocalizedStringKey {
state.allStepsSucceeded ? "accountSuccessDescription" : "accountPartialSuccessDescription"
}
}

/// The four numbered steps with their corresponding text.
fileprivate struct CompletedStepsView: View {
let state: OnboardingState

init(_ state: OnboardingState) {
self.state = state
}

var body: some View {
VStack(alignment: .leading, spacing: 50) {
StepView(completed: true, label: "privateKeyHeadline")
StepView(completed: true, label: "publicKeyHeadline")
StepView(completed: state.displayNameSucceeded, label: "displayNameHeadline")
StepView(completed: state.usernameSucceeded, label: "usernameHeadline")
}
.background(
ConnectingLine()
.offset(x: 8)
.stroke(Color.numberedStepBackground, lineWidth: 4),
alignment: .leading
)
}
}

/// A view containing an index with a circle background and some text.
fileprivate struct StepView: View {
let completed: Bool
let label: LocalizedStringKey

var body: some View {
HStack(alignment: .center, spacing: 20) {
Group {
if completed {
Image(systemName: "checkmark")
} else {
Image(systemName: "xmark")
}
}
.fontWeight(.bold)
.foregroundStyle(Color.primaryTxt)
.frame(width: 16)
.background(
Group {
if completed {
Circle()
.fill(LinearGradient.verticalAccentPrimary)
} else {
Circle()
.fill(Color.numberedStepBackground)
}
}
.frame(width: 30, height: 30)
)

Text(label)
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(Color.primaryTxt)
}
}
}

/// Custom shape for the vertical connecting line
fileprivate struct ConnectingLine: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let startPoint = CGPoint(x: rect.minX, y: 0)
let endPoint = CGPoint(x: rect.minX, y: rect.maxY)

path.move(to: startPoint)
path.addLine(to: endPoint)

return path
}
}

#Preview("All steps completed") {
var state = OnboardingState()
state.displayNameSucceeded = true
state.usernameSucceeded = true

return AccountSuccessView()
.environment(state)
.inject(previewData: PreviewData())
}

#Preview("Some steps failed") {
AccountSuccessView()
.environment(OnboardingState())
.inject(previewData: PreviewData())
}
5 changes: 4 additions & 1 deletion Nos/Views/Onboarding/DisplayNameView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ struct DisplayNameView: View {
}
}
.navigationBarHidden(true)
.alert("errorConnecting", isPresented: $showError) {
.alert("", isPresented: $showError) {
Button {
nextStep()
} label: {
Text("skipForNow")
}
} message: {
Text("errorConnecting")
}
}

Expand Down Expand Up @@ -79,6 +81,7 @@ struct DisplayNameView: View {
do {
try viewContext.save()
try await currentUser.publishMetadata()
state.displayNameSucceeded = true
nextStep()
} catch {
crashReporting.report(error)
Expand Down
16 changes: 16 additions & 0 deletions Nos/Views/Onboarding/OnboardingView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SwiftUI

/// The state of onboarding, tracking the flow, steps, and success or failure of a few specific tasks.
@Observable final class OnboardingState {
var flow: OnboardingFlow = .createAccount
var step: OnboardingStep = .onboardingStart {
Expand All @@ -8,6 +9,17 @@ import SwiftUI
}
}
var path = NavigationPath()

/// Whether the user succeeded in setting their display name
var displayNameSucceeded = false

/// Whether the user succeeded in setting their username
var usernameSucceeded = false

/// Whether the user succeeded in all steps of onboarding
var allStepsSucceeded: Bool {
displayNameSucceeded && usernameSucceeded
}
}

enum OnboardingFlow {
Expand All @@ -24,6 +36,7 @@ enum OnboardingStep {
case publicKey
case displayName
case username
case accountSuccess
case buildYourNetwork
case login
}
Expand Down Expand Up @@ -65,6 +78,9 @@ struct OnboardingView: View {
case .username:
UsernameView()
.environment(state)
case .accountSuccess:
AccountSuccessView()
.environment(state)
case .login:
OnboardingLoginView(completion: completion)
case .buildYourNetwork:
Expand Down
8 changes: 5 additions & 3 deletions Nos/Views/Onboarding/UsernameView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ struct UsernameView: View {

@State private var username = ""
@State private var usernameState: UsernameViewState = .idle
@State private var saveError: SaveProfileError?

private var showAlert: Binding<Bool> {
Binding {
Expand Down Expand Up @@ -57,12 +56,14 @@ struct UsernameView: View {
}
}
.navigationBarHidden(true)
.alert("errorConnecting", isPresented: showAlert) {
.alert("", isPresented: showAlert) {
Button {
nextStep()
} label: {
Text("skipForNow")
}
} message: {
Text("errorConnecting")
}
}

Expand Down Expand Up @@ -119,7 +120,7 @@ struct UsernameView: View {
}

func nextStep() {
state.step = .buildYourNetwork
state.step = .accountSuccess
}

/// Checks whether the username is available and saves it. Updates `usernameState` based on the result.
Expand Down Expand Up @@ -170,6 +171,7 @@ struct UsernameView: View {
relays: relays
)
usernameState = .claimed
state.usernameSucceeded = true
nextStep()
} catch {
crashReporting.report(error)
Expand Down

0 comments on commit 8f7b510

Please sign in to comment.