damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | LICENSE

commit 2920325639b4ae8bd4cf51a0835d2c06c581d10f
parent 0f453c39e6a9082705cac0e0e20f88f200e90d07
Author: William Casarin <jb55@jb55.com>
Date:   Sat, 21 May 2022 19:44:04 -0700

initial CreateAccountView

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
M.gitignore | 1-
Mdamus.xcodeproj/project.pbxproj | 20++++++++++++++++++++
Mdamus/ContentView.swift | 29++---------------------------
Adamus/Models/CreateAccountModel.swift | 40++++++++++++++++++++++++++++++++++++++++
Mdamus/Util/ImageCache.swift | 2+-
Adamus/Util/Keys.swift | 43+++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/CreateAccountView.swift | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/EventView.swift | 2+-
Mdamus/Views/FollowingView.swift | 2+-
Mdamus/Views/PostView.swift | 6+++---
Mdamus/Views/ProfilePicView.swift | 28++++++++++++++++++++--------
Adamus/Views/ProfilePictureSelector.swift | 30++++++++++++++++++++++++++++++
Mdamus/Views/ProfileView.swift | 2+-
Adamus/Views/SaveKeysView.swift | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/SetupView.swift | 101+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mdamus/damusApp.swift | 26++++++++++++++++----------
MdamusTests/LikeTests.swift | 4+++-
17 files changed, 502 insertions(+), 89 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,4 +1,3 @@ xcuserdata -Preview\ Content damus/TestingPrivate.swift .DS_Store diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -14,6 +14,11 @@ 4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F94280F6C78000448DE /* ReplyQuoteView.swift */; }; 4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F96280F8E02000448DE /* ThreadView.swift */; }; 4C285C8228385570008A31F1 /* CarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8128385570008A31F1 /* CarouselView.swift */; }; + 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8328385690008A31F1 /* CreateAccountView.swift */; }; + 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */; }; + 4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */; }; + 4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; }; + 4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; }; 4C363A8428233689006E126D /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; }; 4C363A8628234FDE006E126D /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8528234FDE006E126D /* ImageCache.swift */; }; 4C363A8828236948006E126D /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; }; @@ -105,6 +110,11 @@ 4C0A3F94280F6C78000448DE /* ReplyQuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyQuoteView.swift; sourceTree = "<group>"; }; 4C0A3F96280F8E02000448DE /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; }; 4C285C8128385570008A31F1 /* CarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselView.swift; sourceTree = "<group>"; }; + 4C285C8328385690008A31F1 /* CreateAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountView.swift; sourceTree = "<group>"; }; + 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountModel.swift; sourceTree = "<group>"; }; + 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePictureSelector.swift; sourceTree = "<group>"; }; + 4C285C8B28398BC6008A31F1 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; }; + 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveKeysView.swift; sourceTree = "<group>"; }; 4C363A8328233689006E126D /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; }; 4C363A8528234FDE006E126D /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; }; 4C363A8728236948006E126D /* BlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksView.swift; sourceTree = "<group>"; }; @@ -219,6 +229,7 @@ 4C363A9B282838B9006E126D /* EventRef.swift */, 4C363AA328296DEE006E126D /* SearchModel.swift */, 4C3AC79A28306D7B00E1F516 /* Contacts.swift */, + 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */, ); path = Models; sourceTree = "<group>"; @@ -252,6 +263,9 @@ 4C3AC7A42836987600E1F516 /* MainTabView.swift */, 4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */, 4C285C8128385570008A31F1 /* CarouselView.swift */, + 4C285C8328385690008A31F1 /* CreateAccountView.swift */, + 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */, + 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */, ); path = Views; sourceTree = "<group>"; @@ -284,6 +298,7 @@ 4C363A8528234FDE006E126D /* ImageCache.swift */, 4C363AA728297703006E126D /* InsertSort.swift */, 4C477C9D282C3A4800033AA3 /* TipCounter.swift */, + 4C285C8B28398BC6008A31F1 /* Keys.swift */, ); path = Util; sourceTree = "<group>"; @@ -502,7 +517,9 @@ 4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */, 4C363A8A28236B57006E126D /* MentionView.swift in Sources */, 4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */, + 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */, + 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */, 4C363A8628234FDE006E126D /* ImageCache.swift in Sources */, 4C75EFB728049D990006080F /* RelayPool.swift in Sources */, @@ -510,6 +527,7 @@ 4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */, 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, 4C363AA228296A7E006E126D /* SearchView.swift in Sources */, + 4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */, 4C75EFB92804A2740006080F /* EventView.swift in Sources */, 4C7FF7D52823313F009601DB /* Mentions.swift in Sources */, 4C363A9828283441006E126D /* TestingPrivate.swift in Sources */, @@ -518,6 +536,7 @@ 4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */, 4C0A3F91280F6528000448DE /* ChatView.swift in Sources */, 4C75EFA627FF87A20006080F /* Nostr.swift in Sources */, + 4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */, 4C75EFB328049D640006080F /* NostrEvent.swift in Sources */, 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */, 4C363A8428233689006E126D /* Parser.swift in Sources */, @@ -533,6 +552,7 @@ 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */, + 4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */, 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */, 4C363A94282704FA006E126D /* Post.swift in Sources */, 4C363A8828236948006E126D /* BlocksView.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -31,6 +31,8 @@ enum ThreadState { } struct ContentView: View { + let pubkey: String + let privkey: String @State var status: String = "Not connected" @State var active_sheet: Sheets? = nil @State var loading: Bool = true @@ -56,8 +58,6 @@ struct ContentView: View { let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() let sub_id = UUID().description - let pubkey = MY_PUBKEY - let privkey = MY_PRIVKEY var LoadingContainer: some View { VStack { @@ -708,28 +708,3 @@ func update_filters_with_since(last_of_kind: [Int: NostrEvent], filters: [NostrF } } -struct Keypair { - let pubkey: String - let privkey: String -} - -func save_keypair(pubkey: String, privkey: String) { - UserDefaults.standard.set(pubkey, forKey: "pubkey") - UserDefaults.standard.set(privkey, forKey: "privkey") -} - -func get_saved_keypair() -> Keypair? { - get_saved_pubkey().flatMap { pubkey in - get_saved_privkey().map { privkey in - return Keypair(pubkey: pubkey, privkey: privkey) - } - } -} - -func get_saved_pubkey() -> String? { - return UserDefaults.standard.string(forKey: "pubkey") -} - -func get_saved_privkey() -> String? { - return UserDefaults.standard.string(forKey: "privkey") -} diff --git a/damus/Models/CreateAccountModel.swift b/damus/Models/CreateAccountModel.swift @@ -0,0 +1,40 @@ +// +// CreateAccountModel.swift +// damus +// +// Created by William Casarin on 2022-05-20. +// + +import Foundation + + +class CreateAccountModel: ObservableObject { + @Published var real_name: String = "" + @Published var nick_name: String = "" + @Published var about: String = "" + @Published var pubkey: String = "" + @Published var privkey: String = "" + + var rendered_name: String { + if real_name.isEmpty { + return nick_name + } + return real_name + } + + init() { + let keypair = generate_new_keypair() + self.pubkey = keypair.pubkey + self.privkey = keypair.privkey + } + + init(real: String, nick: String, about: String) { + let keypair = generate_new_keypair() + self.pubkey = keypair.pubkey + self.privkey = keypair.privkey + + self.real_name = real + self.nick_name = nick + self.about = about + } +} diff --git a/damus/Util/ImageCache.swift b/damus/Util/ImageCache.swift @@ -54,7 +54,7 @@ class ImageCache { func insert(_ image: UIImage?, for url: URL) { guard let image = image else { return remove(for: url) } - let decodedImage = image.decodedImage(Int(PFP_SIZE!)) + let decodedImage = image.decodedImage(Int(PFP_SIZE)) lock.lock(); defer { lock.unlock() } cache.setObject(decodedImage, forKey: url as AnyObject) } diff --git a/damus/Util/Keys.swift b/damus/Util/Keys.swift @@ -0,0 +1,43 @@ +// +// Keys.swift +// damus +// +// Created by William Casarin on 2022-05-21. +// + +import Foundation +import secp256k1 + +struct Keypair { + let pubkey: String + let privkey: String +} + +func generate_new_keypair() -> Keypair { + let key = try! secp256k1.Signing.PrivateKey() + let privkey = hex_encode(key.rawRepresentation) + let pubkey = hex_encode(Data(key.publicKey.xonlyKeyBytes)) + print("generating privkey:\(privkey) pubkey:\(pubkey)") + return Keypair(pubkey: pubkey, privkey: privkey) +} + +func save_keypair(pubkey: String, privkey: String) { + UserDefaults.standard.set(pubkey, forKey: "pubkey") + UserDefaults.standard.set(privkey, forKey: "privkey") +} + +func get_saved_keypair() -> Keypair? { + get_saved_pubkey().flatMap { pubkey in + get_saved_privkey().map { privkey in + return Keypair(pubkey: pubkey, privkey: privkey) + } + } +} + +func get_saved_pubkey() -> String? { + return UserDefaults.standard.string(forKey: "pubkey") +} + +func get_saved_privkey() -> String? { + return UserDefaults.standard.string(forKey: "privkey") +} diff --git a/damus/Views/CreateAccountView.swift b/damus/Views/CreateAccountView.swift @@ -0,0 +1,142 @@ +// +// CreateAccountView.swift +// damus +// +// Created by William Casarin on 2022-05-20. +// + +import SwiftUI + +struct CreateAccountView: View { + @StateObject var account: CreateAccountModel = CreateAccountModel() + @State var is_light: Bool = false + @State var is_done: Bool = false + + func FormTextInput(_ title: String, text: Binding<String>) -> some View { + return TextField("", text: text) + .placeholder(when: text.wrappedValue.isEmpty) { + Text(title).foregroundColor(.white.opacity(0.4)) + } + .padding() + .background { + RoundedRectangle(cornerRadius: 4.0).opacity(0.2) + } + .foregroundColor(.white) + .font(.body.bold()) + } + + func FormLabel(_ title: String, optional: Bool = false) -> some View { + return HStack { + Text(title) + .bold() + .foregroundColor(.white) + if optional { + Text("optional") + .font(.callout) + .foregroundColor(.white.opacity(0.5)) + } + } + } + + func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View { + return VStack(alignment: .leading, spacing: 10.0, content: content) + } + + func regen_key() { + let keypair = generate_new_keypair() + self.account.pubkey = keypair.pubkey + self.account.privkey = keypair.privkey + } + + var body: some View { + ZStack(alignment: .top) { + DamusGradient() + + VStack { + Text("Create Account") + .font(.title.bold()) + .foregroundColor(.white) + + ProfilePictureSelector(pubkey: account.pubkey) + + HStack(alignment: .top) { + VStack { + Text(" ") + .foregroundColor(.white) + } + VStack { + SignupForm { + FormLabel("Username") + HStack(spacing: 0.0) { + Text("@") + .foregroundColor(.white) + .padding(.leading, -25.0) + + FormTextInput("satoshi", text: $account.nick_name) + .textInputAutocapitalization(.never) + + } + + FormLabel("Display Name", optional: true) + FormTextInput("Satoshi Nakamoto", text: $account.real_name) + .textInputAutocapitalization(.words) + + FormLabel("About", optional: true) + FormTextInput("Creator(s) of Bitcoin. Absolute legend.", text: $account.about) + + FormLabel("Account ID") + .onTapGesture { + regen_key() + } + + KeyInput($account.pubkey) + .onTapGesture { + regen_key() + } + } + } + } + + NavigationLink(destination: SaveKeysView(account: account), isActive: $is_done) { + EmptyView() + } + DamusWhiteButton("Create") { + self.is_done = true + } + .padding() + } + .padding(.leading, 14.0) + .padding(.trailing, 20.0) + + } + .navigationBarTitleDisplayMode(.inline) + } +} + +extension View { + func placeholder<Content: View>( + when shouldShow: Bool, + alignment: Alignment = .leading, + @ViewBuilder placeholder: () -> Content) -> some View { + + ZStack(alignment: alignment) { + placeholder().opacity(shouldShow ? 1 : 0) + self + } + } +} + +struct CreateAccountView_Previews: PreviewProvider { + static var previews: some View { + let model = CreateAccountModel(real: "", nick: "jb55", about: "") + return CreateAccountView(account: model) + } +} + +func KeyInput(_ text: Binding<String>) -> some View { + return Text("\(text.wrappedValue)") + .textSelection(.enabled) + .font(.callout.monospaced()) + .foregroundColor(.white) +} + diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift @@ -72,7 +72,7 @@ struct EventView: View { let pv = ProfileView(damus_state: damus, profile: pmodel) NavigationLink(destination: pv) { - ProfilePicView(pubkey: event.pubkey, size: PFP_SIZE!, highlight: highlight, image_cache: damus.image_cache, profiles: damus.profiles) + ProfilePicView(pubkey: event.pubkey, size: PFP_SIZE, highlight: highlight, image_cache: damus.image_cache, profiles: damus.profiles) } Spacer() diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift @@ -17,7 +17,7 @@ struct FollowUserView: View { let pv = ProfileView(damus_state: damus_state, profile: pmodel) NavigationLink(destination: pv) { - ProfilePicView(pubkey: pubkey, size: PFP_SIZE!, highlight: .none, image_cache: damus_state.image_cache, profiles: damus_state.profiles) + ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, image_cache: damus_state.image_cache, profiles: damus_state.profiles) } VStack(alignment: .leading) { diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift @@ -56,9 +56,6 @@ struct PostView: View { HStack(alignment: .top) { ZStack(alignment: .leading) { - TextEditor(text: $post) - .focused($focus) - if self.post == "" { VStack { Text("What's happening?") @@ -67,6 +64,9 @@ struct PostView: View { Spacer() } } + + TextEditor(text: $post) + .focused($focus) } diff --git a/damus/Views/ProfilePicView.swift b/damus/Views/ProfilePicView.swift @@ -7,8 +7,7 @@ import SwiftUI -let PFP_SIZE: CGFloat? = 52.0 -let CORNER_RADIUS: CGFloat = 32 +let PFP_SIZE: CGFloat = 52.0 func id_to_color(_ id: String) -> Color { return hex_to_rgb(id) @@ -47,9 +46,9 @@ struct ProfilePicView: View { } var Placeholder: some View { - PlaceholderColor.opacity(0.5) + PlaceholderColor .frame(width: size, height: size) - .cornerRadius(CORNER_RADIUS) + .clipShape(Circle()) .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) .padding(2) } @@ -100,14 +99,27 @@ struct ProfilePicView: View { } } -/* +func make_preview_profiles(_ pubkey: String) -> Profiles { + let profiles = Profiles() + let picture = "http://cdn.jb55.com/img/red-me.jpg" + let profile = Profile(name: "Will", about: "It's me", picture: picture) + let ts_profile = TimestampedProfile(profile: profile, timestamp: 0) + profiles.add(id: pubkey, profile: ts_profile) + return profiles +} + struct ProfilePicView_Previews: PreviewProvider { + static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846" + static var previews: some View { - ProfilePicView(picture: "http://cdn.jb55.com/img/red-me.jpg", size: 64, highlight: .none) + ProfilePicView( + pubkey: pubkey, + size: 100, + highlight: .none, + image_cache: ImageCache(), + profiles: make_preview_profiles(pubkey)) } } - */ - func hex_to_rgb(_ hex: String) -> Color { guard hex.count >= 6 else { diff --git a/damus/Views/ProfilePictureSelector.swift b/damus/Views/ProfilePictureSelector.swift @@ -0,0 +1,30 @@ +// +// ProfilePictureSelector.swift +// damus +// +// Created by William Casarin on 2022-05-20. +// + +import SwiftUI + +struct ProfilePictureSelector: View { + let pubkey: String + + var body: some View { + let highlight: Highlight = .custom(Color.white, 2.0) + ZStack { + ProfilePicView(pubkey: pubkey, size: 80.0, highlight: highlight, image_cache: ImageCache(), profiles: Profiles()) + + Image(systemName: "camera") + .font(.title) + .foregroundColor(.white) + } + } +} + +struct ProfilePictureSelector_Previews: PreviewProvider { + static var previews: some View { + let test_pubkey = "ff48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846" + ProfilePictureSelector(pubkey: test_pubkey) + } +} diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift @@ -72,7 +72,7 @@ struct ProfileView: View { VStack(alignment: .leading) { let data = damus_state.profiles.lookup(id: profile.pubkey) HStack(alignment: .top) { - ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE!, highlight: .custom(Color.black, 2), image_cache: damus_state.image_cache, profiles: damus_state.profiles) + ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .custom(Color.black, 2), image_cache: damus_state.image_cache, profiles: damus_state.profiles) Spacer() diff --git a/damus/Views/SaveKeysView.swift b/damus/Views/SaveKeysView.swift @@ -0,0 +1,113 @@ +// +// SaveKeysView.swift +// damus +// +// Created by William Casarin on 2022-05-21. +// + +import SwiftUI + +struct SaveKeysView: View { + let account: CreateAccountModel + @State var is_done: Bool = false + @State var pub_copied: Bool = false + @State var priv_copied: Bool = false + + var body: some View { + ZStack(alignment: .top) { + DamusGradient() + + VStack(alignment: .center) { + Text("Welcome, \(account.rendered_name)!") + .font(.title.bold()) + .foregroundColor(.white) + .padding(.bottom, 10) + + 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.") + .foregroundColor(.white) + .padding(.bottom, 10) + + Text("Public Key") + .font(.title2.bold()) + .foregroundColor(.white) + .padding(.bottom, 10) + + Text("This is your account ID, you can give this to your friends so that they can follow you") + .foregroundColor(.white) + .padding(.bottom, 10) + + SaveKeyView(text: account.pubkey, is_copied: $pub_copied) + .padding(.bottom, 10) + + Text("Private Key") + .font(.title2.bold()) + .foregroundColor(.white) + .padding(.bottom, 10) + + 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!") + .foregroundColor(.white) + .padding(.bottom, 10) + + SaveKeyView(text: account.privkey, is_copied: $priv_copied) + .padding(.bottom, 10) + + if pub_copied && priv_copied { + DamusWhiteButton("Let's go!") { + save_keypair(pubkey: account.pubkey, privkey: account.privkey) + notify(.login, ()) + } + } + } + .padding(20) + } + } +} + +struct SaveKeyView: View { + let text: String + @Binding var is_copied: Bool + + func copy_text() { + UIPasteboard.general.string = text + is_copied = true + } + + var body: some View { + HStack { + Button(action: copy_text) { + Label("", systemImage: is_copied ? "checkmark.circle.fill" : "doc.on.doc") + .foregroundColor(is_copied ? .green : .white) + .background { + if is_copied { + Circle() + .foregroundColor(.white) + .frame(width: 25, height: 25, alignment: .center) + .padding(.leading, -8) + .padding(.top, 1) + } else { + EmptyView() + } + } + } + + Text(text) + .padding(5) + .background { + RoundedRectangle(cornerRadius: 4.0).opacity(0.2) + } + .textSelection(.enabled) + .font(.callout.monospaced()) + .foregroundColor(.white) + .onTapGesture { + copy_text() + } + } + } +} + +struct SaveKeysView_Previews: PreviewProvider { + static var previews: some View { + let model = CreateAccountModel(real: "William", nick: "jb55", about: "I'm me") + SaveKeysView(account: model) + } +} diff --git a/damus/Views/SetupView.swift b/damus/Views/SetupView.swift @@ -20,50 +20,80 @@ let damus_grad_c2 = hex_col(r: 0x7f, g: 0x35, b: 0xab) let damus_grad_c3 = hex_col(r: 0xff, g: 0x0b, b: 0xd6) let damus_grad = [damus_grad_c1, damus_grad_c2, damus_grad_c3] +enum SetupState { + case home + case create_account + case login +} + +struct DamusGradient: View { + var body: some View { + LinearGradient(colors: damus_grad, startPoint: .bottomLeading, endPoint: .topTrailing) + .edgesIgnoringSafeArea([.top,.bottom]) + } +} + struct SetupView: View { + @State var state: SetupState? = .home + var body: some View { - ZStack { - LinearGradient(colors: damus_grad, startPoint: .bottomLeading, endPoint: .topTrailing) - .edgesIgnoringSafeArea([.top,.bottom]) - - VStack(alignment: .center) { - Image("logo-nobg") - .resizable() - .frame(width: 128.0, height: 128.0, alignment: .center) - .padding([.top], 20.0) - Text("Damus") - .font(Font.custom("Nunito", size: 50.0)) - .kerning(-2) - .foregroundColor(.white) - - CarouselView() + NavigationView { + ZStack { + DamusGradient() - Spacer() - - Button("Create Account") { - print("Create Account") - } - .font(.body.bold()) - .foregroundColor(.white) - .frame(width: 300, height: 50) - .background( - RoundedRectangle(cornerRadius: 4.0) - .stroke(Color.white, lineWidth: 2.0) - .background(Color.white.opacity(0.15)) - ) - - Button("Login") { - notify(.login, ()) + VStack(alignment: .center) { + NavigationLink(destination: CreateAccountView(), tag: .create_account, selection: $state ) { + EmptyView() + } + + Image("logo-nobg") + .resizable() + .frame(width: 128.0, height: 128.0, alignment: .center) + .padding([.top], 20.0) + Text("Damus") + .font(Font.custom("Nunito", size: 50.0)) + .kerning(-2) + .foregroundColor(.white) + + CarouselView() + + Spacer() + + DamusWhiteButton("Create Account") { + self.state = .create_account + } + + Button("Login") { + notify(.login, ()) + } + .padding([.top, .bottom], 20) + .foregroundColor(.white) + + Spacer() } - .foregroundColor(.white) - .padding([.top], 20) - - Spacer() } + .padding(.top, -80) } + .navigationBarTitleDisplayMode(.inline) + .navigationViewStyle(StackNavigationViewStyle()) } } +func DamusWhiteButton(_ title: String, action: @escaping () -> ()) -> some View { + return Button(action: action) { + Text(title) + .frame(width: 300, height: 50) + .font(.body.bold()) + .contentShape(Rectangle()) + .foregroundColor(.white) + .background( + RoundedRectangle(cornerRadius: 4.0) + .stroke(Color.white, lineWidth: 2.0) + .background(Color.white.opacity(0.15)) + ) + } + +} struct SetupView_Previews: PreviewProvider { static var previews: some View { @@ -75,3 +105,4 @@ struct SetupView_Previews: PreviewProvider { } } } + diff --git a/damus/damusApp.swift b/damus/damusApp.swift @@ -21,21 +21,27 @@ struct damusApp: App { struct MainView: View { @State var needs_setup = true; + @State var keypair: Keypair? = nil; var body: some View { - if needs_setup { - SetupView() - .onReceive(handle_notify(.login)) { notif in - needs_setup = false - } - } else { - ContentView() + Group { + if let kp = keypair, !needs_setup { + ContentView(pubkey: kp.pubkey, privkey: kp.privkey) + } else { + SetupView() + .onReceive(handle_notify(.login)) { notif in + needs_setup = false + keypair = get_saved_keypair() + } + } + } + .onAppear { + keypair = get_saved_keypair() } } } -func needs_setup() -> Bool { - let _ = get_saved_privkey() - return true +func needs_setup() -> Keypair? { + return get_saved_keypair() } diff --git a/damusTests/LikeTests.swift b/damusTests/LikeTests.swift @@ -19,10 +19,12 @@ class LikeTests: XCTestCase { } func testLikeHasNotification() throws { + let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe" + let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2" let liked = NostrEvent(content: "awesome #[0] post", pubkey: "orig_pk", tags: [["p", "cindy"], ["e", "bob"]]) liked.calculate_id() let id = liked.id - let like_ev = make_like_event(pubkey: "pubkey", liked: liked)! + let like_ev = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked) XCTAssertTrue(like_ev.references(id: "orig_pk", key: "p")) XCTAssertTrue(like_ev.references(id: "cindy", key: "p"))