Merge pull request #2308 from ericholguin/simplify-onboarding
ux: Simplify Onboarding
This commit is contained in:
@@ -9,31 +9,31 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
class CreateAccountModel: ObservableObject {
|
class CreateAccountModel: ObservableObject {
|
||||||
@Published var real_name: String = ""
|
@Published var display_name: String = ""
|
||||||
@Published var nick_name: String = ""
|
@Published var name: String = ""
|
||||||
@Published var about: String = ""
|
@Published var about: String = ""
|
||||||
@Published var pubkey: Pubkey = .empty
|
@Published var pubkey: Pubkey = .empty
|
||||||
@Published var privkey: Privkey = .empty
|
@Published var privkey: Privkey = .empty
|
||||||
@Published var profile_image: URL? = nil
|
@Published var profile_image: URL? = nil
|
||||||
|
|
||||||
var rendered_name: String {
|
var rendered_name: String {
|
||||||
if real_name.isEmpty {
|
if display_name.isEmpty {
|
||||||
return nick_name
|
return name
|
||||||
}
|
}
|
||||||
return real_name
|
return display_name
|
||||||
}
|
}
|
||||||
|
|
||||||
var keypair: Keypair {
|
var keypair: Keypair {
|
||||||
return Keypair(pubkey: self.pubkey, privkey: self.privkey)
|
return Keypair(pubkey: self.pubkey, privkey: self.privkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(real: String = "", nick: String = "", about: String = "") {
|
init(display_name: String = "", name: String = "", about: String = "") {
|
||||||
let keypair = generate_new_keypair()
|
let keypair = generate_new_keypair()
|
||||||
self.pubkey = keypair.pubkey
|
self.pubkey = keypair.pubkey
|
||||||
self.privkey = keypair.privkey
|
self.privkey = keypair.privkey
|
||||||
|
|
||||||
self.real_name = real
|
self.display_name = display_name
|
||||||
self.nick_name = nick
|
self.name = name
|
||||||
self.about = about
|
self.about = about
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,68 +25,44 @@ struct CreateAccountView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
VStack {
|
VStack {
|
||||||
|
Spacer()
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
EditPictureControl(uploader: .nostrBuild, pubkey: account.pubkey, image_url: $account.profile_image , uploadObserver: profileUploadObserver, callback: uploadedProfilePicture)
|
|
||||||
|
EditPictureControl(uploader: .nostrBuild, pubkey: account.pubkey, size: 75, setup: true, image_url: $account.profile_image , uploadObserver: profileUploadObserver, callback: uploadedProfilePicture)
|
||||||
Text("Public Key", comment: "Label to indicate the public key of the account.")
|
.shadow(radius: 2)
|
||||||
|
.padding(.top, 100)
|
||||||
|
|
||||||
|
Text("Add Photo", comment: "Label to indicate user can add a photo.")
|
||||||
.bold()
|
.bold()
|
||||||
.padding()
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.onTapGesture {
|
|
||||||
regen_key()
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyText($account.pubkey)
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
.onTapGesture {
|
|
||||||
regen_key()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 250, alignment: .center)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.fill(DamusColors.adaptableGrey, strokeBorder: .gray.opacity(0.5), lineWidth: 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SignupForm {
|
SignupForm {
|
||||||
FormLabel(NSLocalizedString("Display name", comment: "Label to prompt display name entry."), optional: true)
|
FormLabel(NSLocalizedString("Name", comment: "Label to prompt name entry."), optional: false)
|
||||||
FormTextInput(NSLocalizedString("Satoshi Nakamoto", comment: "Name of Bitcoin creator(s)."), text: $account.real_name)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
FormTextInput(NSLocalizedString("Satoshi Nakamoto", comment: "Name of Bitcoin creator(s)."), text: $account.name)
|
||||||
.textInputAutocapitalization(.words)
|
.textInputAutocapitalization(.words)
|
||||||
|
|
||||||
FormLabel(NSLocalizedString("About", comment: "Label to prompt for about text entry for user to describe about themself."), optional: true)
|
FormLabel(NSLocalizedString("Bio", comment: "Label to prompt bio entry for user to describe themself."), optional: true)
|
||||||
FormTextInput(NSLocalizedString("Creator(s) of Bitcoin. Absolute legend.", comment: "Example description about Bitcoin creator(s), Satoshi Nakamoto."), text: $account.about)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
FormTextInput(NSLocalizedString("Absolute legend.", comment: "Example Bio"), text: $account.about)
|
||||||
}
|
}
|
||||||
.padding(.top, 10)
|
.padding(.top, 25)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
nav.push(route: Route.SaveKeys(account: account))
|
nav.push(route: Route.SaveKeys(account: account))
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Create account now", comment: "Button to create account.")
|
Text("Next", comment: "Button to continue with account creation.")
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
}
|
}
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
.disabled(profileUploadObserver.isLoading)
|
.disabled(profileUploadObserver.isLoading || account.name.isEmpty)
|
||||||
.opacity(profileUploadObserver.isLoading ? 0.5 : 1)
|
.opacity(profileUploadObserver.isLoading || account.name.isEmpty ? 0.5 : 1)
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("By signing up, you agree to our ", comment: "Ask the user if they already have an account on Nostr")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
nav.push(route: Route.EULA)
|
|
||||||
}, label: {
|
|
||||||
Text("EULA")
|
|
||||||
.font(.subheadline)
|
|
||||||
})
|
|
||||||
.padding(.vertical, 5)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
LoginPrompt()
|
LoginPrompt()
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
|
||||||
@@ -94,8 +70,8 @@ struct CreateAccountView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
.dismissKeyboardOnTap()
|
.dismissKeyboardOnTap()
|
||||||
.navigationTitle("Create account")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.navigationBarItems(leading: BackNav())
|
.navigationBarItems(leading: BackNav())
|
||||||
@@ -111,7 +87,7 @@ struct LoginPrompt: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Already on Nostr?", comment: "Ask the user if they already have an account on Nostr")
|
Text("Already on Nostr?", comment: "Ask the user if they already have an account on Nostr")
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
|
||||||
Button(NSLocalizedString("Login", comment: "Button to navigate to login view.")) {
|
Button(NSLocalizedString("Login", comment: "Button to navigate to login view.")) {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
@@ -127,8 +103,8 @@ struct BackNav: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Image("chevron-left")
|
Image("chevron-left")
|
||||||
.foregroundColor(DamusColors.adaptableBlack)
|
.foregroundColor(DamusColors.adaptableBlack)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,20 +124,11 @@ extension View {
|
|||||||
|
|
||||||
struct CreateAccountView_Previews: PreviewProvider {
|
struct CreateAccountView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let model = CreateAccountModel(real: "", nick: "jb55", about: "")
|
let model = CreateAccountModel(display_name: "", name: "jb55", about: "")
|
||||||
return CreateAccountView(account: model, nav: .init())
|
return CreateAccountView(account: model, nav: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeyText(_ pubkey: Binding<Pubkey>) -> some View {
|
|
||||||
let bechkey = bech32_encode(hrp: PUBKEY_HRP, pubkey.wrappedValue.bytes)
|
|
||||||
return Text(bechkey)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.font(.callout.monospaced())
|
|
||||||
.foregroundStyle(DamusLogoGradient.gradient)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
||||||
return TextField("", text: text)
|
return TextField("", text: text)
|
||||||
.placeholder(when: text.wrappedValue.isEmpty) {
|
.placeholder(when: text.wrappedValue.isEmpty) {
|
||||||
@@ -171,6 +138,10 @@ func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
|||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: 12)
|
||||||
.stroke(.gray.opacity(0.5), lineWidth: 1)
|
.stroke(.gray.opacity(0.5), lineWidth: 1)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.foregroundColor(.damusAdaptableWhite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.font(.body.bold())
|
.font(.body.bold())
|
||||||
}
|
}
|
||||||
@@ -183,6 +154,10 @@ func FormLabel(_ title: String, optional: Bool = false) -> some View {
|
|||||||
Text("optional", comment: "Label indicating that a form input is optional.")
|
Text("optional", comment: "Label indicating that a form input is optional.")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
|
} else {
|
||||||
|
Text("required", comment: "Label indicating that a form input is required.")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ struct LoginView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
VStack {
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
SignInHeader()
|
SignInHeader()
|
||||||
.padding(.top, 100)
|
|
||||||
|
|
||||||
SignInEntry(key: $key, shouldSaveKey: $shouldSaveKey)
|
SignInEntry(key: $key, shouldSaveKey: $shouldSaveKey)
|
||||||
|
|
||||||
@@ -112,8 +113,9 @@ struct LoginView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.padding(.bottom, 50)
|
||||||
}
|
}
|
||||||
.background(DamusBackground(maxHeight: 350), alignment: .top)
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
credential_handler.check_credentials()
|
credential_handler.check_credentials()
|
||||||
}
|
}
|
||||||
@@ -320,9 +322,13 @@ struct KeyInput: View {
|
|||||||
}
|
}
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.overlay {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: 12)
|
||||||
.stroke(.gray, lineWidth: 1)
|
.stroke(.gray, lineWidth: 1)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.foregroundColor(.damusAdaptableWhite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,11 +343,12 @@ struct SignInHeader: View {
|
|||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
|
|
||||||
Text("Sign in", comment: "Title of view to log into an account.")
|
Text("Sign in", comment: "Title of view to log into an account.")
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.font(.system(size: 32, weight: .bold))
|
.font(.system(size: 32, weight: .bold))
|
||||||
.padding(.bottom, 5)
|
.padding(.bottom, 5)
|
||||||
|
|
||||||
Text("Welcome to the social network you control", comment: "Welcome text")
|
Text("Welcome to the social network you control", comment: "Welcome text")
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
.foregroundColor(DamusColors.neutral6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,6 +360,7 @@ struct SignInEntry: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Enter your account key", comment: "Prompt for user to enter an account key to login.")
|
Text("Enter your account key", comment: "Prompt for user to enter an account key to login.")
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
@@ -444,7 +452,9 @@ struct LoginView_Previews: PreviewProvider {
|
|||||||
let bech32_pubkey = "KeyInput"
|
let bech32_pubkey = "KeyInput"
|
||||||
Group {
|
Group {
|
||||||
LoginView(key: pubkey, nav: .init())
|
LoginView(key: pubkey, nav: .init())
|
||||||
|
.previewDevice(PreviewDevice(rawValue: "iPhone SE (3rd generation)"))
|
||||||
LoginView(key: bech32_pubkey, nav: .init())
|
LoginView(key: bech32_pubkey, nav: .init())
|
||||||
|
.previewDevice(PreviewDevice(rawValue: "iPhone 15 Pro Max"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
class ImageUploadingObserver: ObservableObject {
|
class ImageUploadingObserver: ObservableObject {
|
||||||
@Published var isLoading: Bool = false
|
@Published var isLoading: Bool = false
|
||||||
@@ -14,6 +15,8 @@ class ImageUploadingObserver: ObservableObject {
|
|||||||
struct EditPictureControl: View {
|
struct EditPictureControl: View {
|
||||||
let uploader: MediaUploader
|
let uploader: MediaUploader
|
||||||
let pubkey: Pubkey
|
let pubkey: Pubkey
|
||||||
|
var size: CGFloat? = 25
|
||||||
|
var setup: Bool? = false
|
||||||
@Binding var image_url: URL?
|
@Binding var image_url: URL?
|
||||||
@ObservedObject var uploadObserver: ImageUploadingObserver
|
@ObservedObject var uploadObserver: ImageUploadingObserver
|
||||||
let callback: (URL?) -> Void
|
let callback: (URL?) -> Void
|
||||||
@@ -43,20 +46,53 @@ struct EditPictureControl: View {
|
|||||||
if uploadObserver.isLoading {
|
if uploadObserver.isLoading {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(CircularProgressViewStyle(tint: DamusColors.purple))
|
.progressViewStyle(CircularProgressViewStyle(tint: DamusColors.purple))
|
||||||
|
.frame(width: size, height: size)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(DamusColors.white.opacity(0.7))
|
.background(DamusColors.white.opacity(0.7))
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
|
.shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
|
||||||
|
} else if let url = image_url {
|
||||||
|
KFAnimatedImage(url)
|
||||||
|
.imageContext(.pfp, disable_animation: false)
|
||||||
|
.onFailure(fallbackUrl: URL(string: robohash(pubkey)), cacheKey: url.absoluteString)
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.configure { view in
|
||||||
|
view.framePreloadCount = 3
|
||||||
|
}
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: (size ?? 25) + 10, height: (size ?? 25) + 10)
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.overlay(Circle().stroke(.white, lineWidth: 4))
|
||||||
} else {
|
} else {
|
||||||
Image("camera")
|
if setup ?? false {
|
||||||
.resizable()
|
Image(systemName: "person")
|
||||||
.scaledToFit()
|
.resizable()
|
||||||
.frame(width: 25, height: 25)
|
.scaledToFit()
|
||||||
.foregroundColor(DamusColors.purple)
|
.frame(width: size, height: size)
|
||||||
.padding(10)
|
.foregroundColor(DamusColors.white)
|
||||||
.background(DamusColors.white.opacity(0.7))
|
.padding(20)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
|
.background {
|
||||||
|
Circle()
|
||||||
|
.fill(PinkGradient, strokeBorder: .white, lineWidth: 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Image("camera")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.foregroundColor(DamusColors.purple)
|
||||||
|
.padding(10)
|
||||||
|
.background(DamusColors.white.opacity(0.7))
|
||||||
|
.clipShape(Circle())
|
||||||
|
.background {
|
||||||
|
Circle()
|
||||||
|
.fill(DamusColors.purple, strokeBorder: .white, lineWidth: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $show_camera) {
|
.sheet(isPresented: $show_camera) {
|
||||||
@@ -110,7 +146,7 @@ struct EditPictureControl_Previews: PreviewProvider {
|
|||||||
let observer = ImageUploadingObserver()
|
let observer = ImageUploadingObserver()
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.gray
|
Color.gray
|
||||||
EditPictureControl(uploader: .nostrBuild, pubkey: test_pubkey, image_url: url, uploadObserver: observer) { _ in
|
EditPictureControl(uploader: .nostrBuild, pubkey: test_pubkey, size: 100, setup: false, image_url: url, uploadObserver: observer) { _ in
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import Security
|
|||||||
struct SaveKeysView: View {
|
struct SaveKeysView: View {
|
||||||
let account: CreateAccountModel
|
let account: CreateAccountModel
|
||||||
let pool: RelayPool = RelayPool(ndb: Ndb()!)
|
let pool: RelayPool = RelayPool(ndb: Ndb()!)
|
||||||
@State var pub_copied: Bool = false
|
|
||||||
@State var priv_copied: Bool = false
|
|
||||||
@State var loading: Bool = false
|
@State var loading: Bool = false
|
||||||
@State var error: String? = nil
|
@State var error: String? = nil
|
||||||
|
|
||||||
@@ -31,81 +29,98 @@ struct SaveKeysView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image("logo-nobg")
|
||||||
|
.resizable()
|
||||||
|
.shadow(color: DamusColors.purple, radius: 2)
|
||||||
|
.frame(width: 56, height: 56, alignment: .center)
|
||||||
|
.padding(.top, 20.0)
|
||||||
|
|
||||||
if account.rendered_name.isEmpty {
|
if account.rendered_name.isEmpty {
|
||||||
Text("Welcome!", comment: "Text to welcome user.")
|
Text("Welcome!", comment: "Text to welcome user.")
|
||||||
.font(.title.bold())
|
.font(.title)
|
||||||
.padding(.bottom, 10)
|
.fontWeight(.heavy)
|
||||||
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
} else {
|
} else {
|
||||||
Text("Welcome, \(account.rendered_name)!", comment: "Text to welcome user.")
|
Text("Welcome, \(account.rendered_name)!", comment: "Text to welcome user.")
|
||||||
.font(.title.bold())
|
.font(.title)
|
||||||
.padding(.bottom, 10)
|
.fontWeight(.heavy)
|
||||||
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus.", comment: "Reminder to user that they should save their account information.")
|
Text("Save your login info?", comment: "Ask user if they want to save their account information.")
|
||||||
.padding(.bottom, 10)
|
.font(.title)
|
||||||
|
.fontWeight(.heavy)
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
.padding(.top, 5)
|
||||||
|
|
||||||
Text("Private Key", comment: "Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account.")
|
Text("We'll save your account key, so you won't need to enter it manually next time you log in.", comment: "Reminder to user that they should save their account information.")
|
||||||
.font(.title2.bold())
|
.font(.system(size: 14))
|
||||||
.padding(.bottom, 10)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
.padding(.top, 2)
|
||||||
|
.padding(.bottom, 100)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
Text("This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!", comment: "Label to describe that a private key is the user's secret account key and what they should do with it.")
|
Spacer()
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
SaveKeyView(text: account.privkey.nsec, textContentType: .newPassword, is_copied: $priv_copied, focus: $privkey_focused)
|
if loading {
|
||||||
.padding(.bottom, 10)
|
ProgressView()
|
||||||
|
.progressViewStyle(.circular)
|
||||||
if priv_copied {
|
} else if let err = error {
|
||||||
if loading {
|
Text("Error: \(err)", comment: "Error message indicating why saving keys failed.")
|
||||||
ProgressView()
|
.foregroundColor(.red)
|
||||||
.progressViewStyle(.circular)
|
|
||||||
} else if let err = error {
|
Button(action: {
|
||||||
Text("Error: \(err)", comment: "Error message indicating why saving keys failed.")
|
complete_account_creation(account)
|
||||||
.foregroundColor(.red)
|
}) {
|
||||||
|
HStack {
|
||||||
Button(action: {
|
Text("Retry", comment: "Button to retry completing account creation after an error occurred.")
|
||||||
complete_account_creation(account)
|
.fontWeight(.semibold)
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text("Retry", comment: "Button to retry completing account creation after an error occurred.")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
|
||||||
}
|
}
|
||||||
.buttonStyle(GradientButtonStyle())
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
.padding(.top, 20)
|
|
||||||
} else {
|
|
||||||
Button(action: {
|
|
||||||
complete_account_creation(account)
|
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text("Let's go!", comment: "Button to complete account creation and start using the app.")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
|
||||||
}
|
|
||||||
.buttonStyle(GradientButtonStyle())
|
|
||||||
.padding(.top, 20)
|
|
||||||
}
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
.padding(.top, 20)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
save_key(account)
|
||||||
|
complete_account_creation(account)
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Save", comment: "Button to save key, complete account creation, and start using the app.")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
complete_account_creation(account)
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Not now", comment: "Button to not save key, complete account creation, and start using the app.")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
|
}
|
||||||
|
.buttonStyle(NeutralButtonStyle(padding: EdgeInsets(top: 15, leading: 15, bottom: 15, trailing: 15), cornerRadius: 12))
|
||||||
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
}
|
}
|
||||||
.background(
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
Image("eula-bg")
|
|
||||||
.resizable()
|
|
||||||
.blur(radius: 70)
|
|
||||||
.ignoresSafeArea(),
|
|
||||||
alignment: .top
|
|
||||||
)
|
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.navigationBarItems(leading: BackNav())
|
.navigationBarItems(leading: BackNav())
|
||||||
.onAppear {
|
|
||||||
// Hack to force keyboard to show up for a short moment and then hiding it to register password autofill flow.
|
}
|
||||||
pubkey_focused = true
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
func save_key(_ account: CreateAccountModel) {
|
||||||
pubkey_focused = false
|
credential_handler.save_credential(pubkey: account.pubkey, privkey: account.privkey)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func complete_account_creation(_ account: CreateAccountModel) {
|
func complete_account_creation(_ account: CreateAccountModel) {
|
||||||
@@ -122,8 +137,6 @@ struct SaveKeysView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.pool.register_handler(sub_id: "signup", handler: handle_event)
|
self.pool.register_handler(sub_id: "signup", handler: handle_event)
|
||||||
|
|
||||||
credential_handler.save_credential(pubkey: account.pubkey, privkey: account.privkey)
|
|
||||||
|
|
||||||
self.loading = true
|
self.loading = true
|
||||||
|
|
||||||
@@ -188,74 +201,13 @@ struct SaveKeysView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SaveKeyView: View {
|
|
||||||
let text: String
|
|
||||||
let textContentType: UITextContentType
|
|
||||||
@Binding var is_copied: Bool
|
|
||||||
var focus: FocusState<Bool>.Binding
|
|
||||||
|
|
||||||
func copy_text() {
|
|
||||||
UIPasteboard.general.string = text
|
|
||||||
is_copied = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
spacerBlock(width: 0, height: 0)
|
|
||||||
Button(action: copy_text) {
|
|
||||||
Label("", image: is_copied ? "check-circle.fill" : "copy2")
|
|
||||||
.foregroundColor(is_copied ? .green : .gray)
|
|
||||||
.background {
|
|
||||||
if is_copied {
|
|
||||||
Circle()
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.padding(.leading, -8)
|
|
||||||
.padding(.top, 1)
|
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField("", text: .constant(text))
|
|
||||||
.padding(5)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 4.0).opacity(0.1)
|
|
||||||
}
|
|
||||||
.textSelection(.enabled)
|
|
||||||
.font(.callout.monospaced())
|
|
||||||
.onTapGesture {
|
|
||||||
copy_text()
|
|
||||||
// Hack to force keyboard to hide. Showing keyboard on text field is necessary to register password autofill flow but the text itself should not be modified.
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
end_editing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.textContentType(textContentType)
|
|
||||||
.deleteDisabled(true)
|
|
||||||
.focused(focus)
|
|
||||||
|
|
||||||
spacerBlock(width: 0, height: 0) /// set a 'width' > 0 here to vary key Text's aspect ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder private func spacerBlock(width: CGFloat, height: CGFloat) -> some View {
|
|
||||||
Color.orange.opacity(1)
|
|
||||||
.frame(width: width, height: height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SaveKeysView_Previews: PreviewProvider {
|
struct SaveKeysView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let model = CreateAccountModel(real: "William", nick: "jb55", about: "I'm me")
|
let model = CreateAccountModel(display_name: "William", name: "jb55", about: "I'm me")
|
||||||
SaveKeysView(account: model)
|
SaveKeysView(account: model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_account_to_metadata(_ model: CreateAccountModel) -> Profile {
|
func create_account_to_metadata(_ model: CreateAccountModel) -> Profile {
|
||||||
return Profile(name: model.nick_name, display_name: model.real_name, about: model.about, picture: model.profile_image?.absoluteString, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nil, damus_donation: nil)
|
return Profile(name: model.name, display_name: model.display_name, about: model.about, picture: model.profile_image?.absoluteString, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nil, damus_donation: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,32 +28,53 @@ struct SetupView: View {
|
|||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.foregroundStyle(DamusLogoGradient.gradient)
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
|
|
||||||
Text("The go-to iOS Nostr client", comment: "Quick description of what Damus is")
|
Text("The social network you control", comment: "Quick description of what Damus is")
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
|
|
||||||
WhatIsNostr()
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
WhyWeNeedNostr()
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
navigationCoordinator.push(route: Route.CreateAccount)
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Create Account", comment: "Button to continue to the create account page.")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
navigationCoordinator.push(route: Route.Login)
|
navigationCoordinator.push(route: Route.Login)
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Let's get started!", comment: "Button to continue to login page.")
|
Text("Sign In", comment: "Button to continue to login page.")
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
}
|
}
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("By continuing you agree to our ")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
navigationCoordinator.push(route: Route.EULA)
|
||||||
|
}, label: {
|
||||||
|
Text("EULA", comment: "End User License Agreement")
|
||||||
|
.font(.subheadline)
|
||||||
|
})
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(DamusBackground(maxHeight: 300), alignment: .top)
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
.navigationDestination(for: Route.self) { route in
|
.navigationDestination(for: Route.self) { route in
|
||||||
route.view(navigationCoordinator: navigationCoordinator, damusState: DamusState.empty)
|
route.view(navigationCoordinator: navigationCoordinator, damusState: DamusState.empty)
|
||||||
}
|
}
|
||||||
@@ -63,61 +84,6 @@ struct SetupView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LearnAboutNostrLink: View {
|
|
||||||
@Environment(\.openURL) var openURL
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Button(action: {
|
|
||||||
openURL(URL(string: "https://nostr.com")!)
|
|
||||||
}, label: {
|
|
||||||
Text("Learn more about Nostr", comment: "Button that opens up a webpage where the user can learn more about Nostr.")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
})
|
|
||||||
|
|
||||||
Image(systemName: "arrow.up.right")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WhatIsNostr: View {
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Image("nostr-logo")
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("What is Nostr?", comment: "Heading text for section describing what is Nostr.")
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.padding(.vertical, 10)
|
|
||||||
|
|
||||||
Text("Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network", comment: "Description about what is Nostr.")
|
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
|
||||||
|
|
||||||
LearnAboutNostrLink()
|
|
||||||
.padding(.top, 10)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WhyWeNeedNostr: View {
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Image("lightbulb")
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("Why we need Nostr?", comment: "Heading text for section describing why Nostr is needed.")
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.padding(.vertical, 10)
|
|
||||||
|
|
||||||
Text("Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken", comment: "Description about why Nostr is needed.")
|
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SetupView_Previews: PreviewProvider {
|
struct SetupView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
Group {
|
Group {
|
||||||
|
|||||||
Reference in New Issue
Block a user