commit 370a5feb4e4484f24a8f3f23794f29cb5e29c62b
parent fe3d976cdbff19ec61efba322d5d39769e8e595d
Author: William Casarin <jb55@jb55.com>
Date: Tue, 9 May 2023 18:50:08 -0700
ui: add Nostr Wallet Connect views
Diffstat:
7 files changed, 310 insertions(+), 27 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -137,7 +137,11 @@
4C75EFB92804A2740006080F /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB82804A2740006080F /* EventView.swift */; };
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; };
4C7D09592A05BEAD00943473 /* KeyboardVisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */; };
+ 4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095C2A098C5D00943473 /* ConnectWalletView.swift */; };
+ 4C7D09602A098C5D00943473 /* WalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095D2A098C5D00943473 /* WalletView.swift */; };
+ 4C7D09622A098D0E00943473 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09612A098D0E00943473 /* WalletConnect.swift */; };
4C7D09662A0AE62100943473 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; };
+ 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */; };
4C7D096D2A0AEA0400943473 /* CodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096A2A0AEA0400943473 /* CodeScanner.swift */; };
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096B2A0AEA0400943473 /* ScannerCoordinator.swift */; };
4C7D096F2A0AEA0400943473 /* ScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D096C2A0AEA0400943473 /* ScannerViewController.swift */; };
@@ -553,7 +557,11 @@
4C75EFB82804A2740006080F /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = "<group>"; };
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; };
4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardVisible.swift; sourceTree = "<group>"; };
+ 4C7D095C2A098C5D00943473 /* ConnectWalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectWalletView.swift; sourceTree = "<group>"; };
+ 4C7D095D2A098C5D00943473 /* WalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletView.swift; sourceTree = "<group>"; };
+ 4C7D09612A098D0E00943473 /* WalletConnect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnect.swift; sourceTree = "<group>"; };
4C7D09652A0AE62100943473 /* AlbyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbyButton.swift; sourceTree = "<group>"; };
+ 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NWCScannerView.swift; sourceTree = "<group>"; };
4C7D096A2A0AEA0400943473 /* CodeScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeScanner.swift; sourceTree = "<group>"; };
4C7D096B2A0AEA0400943473 /* ScannerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScannerCoordinator.swift; sourceTree = "<group>"; };
4C7D096C2A0AEA0400943473 /* ScannerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScannerViewController.swift; sourceTree = "<group>"; };
@@ -924,6 +932,7 @@
isa = PBXGroup;
children = (
4C7D09692A0AEA0400943473 /* CodeScanner */,
+ 4C7D095A2A098C5C00943473 /* Wallet */,
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
4C1A9A1B29DDCF8B00516EAC /* Settings */,
4CFF8F6129CC9A80008DB934 /* Images */,
@@ -1005,6 +1014,16 @@
path = Nostr;
sourceTree = "<group>";
};
+ 4C7D095A2A098C5C00943473 /* Wallet */ = {
+ isa = PBXGroup;
+ children = (
+ 4C7D095C2A098C5D00943473 /* ConnectWalletView.swift */,
+ 4C7D095D2A098C5D00943473 /* WalletView.swift */,
+ 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */,
+ );
+ path = Wallet;
+ sourceTree = "<group>";
+ };
4C7D09692A0AEA0400943473 /* CodeScanner */ = {
isa = PBXGroup;
children = (
@@ -1027,6 +1046,7 @@
4C7FF7D628233637009601DB /* Util */ = {
isa = PBXGroup;
children = (
+ 4C7D09612A098D0E00943473 /* WalletConnect.swift */,
4C198DF329F88D23004C165C /* Images */,
4C198DEA29F88C6B004C165C /* BlurHash */,
4CE4F0F329D779B5005914DB /* PostBox.swift */,
@@ -1622,6 +1642,7 @@
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */,
4C8D00CA29DF80350036AF10 /* TruncatedText.swift in Sources */,
4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */,
+ 4C7D09602A098C5D00943473 /* WalletView.swift in Sources */,
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */,
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
@@ -1664,6 +1685,7 @@
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
+ 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
@@ -1695,6 +1717,7 @@
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */,
4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */,
4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */,
+ 4C7D09622A098D0E00943473 /* WalletConnect.swift in Sources */,
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
@@ -1710,6 +1733,7 @@
4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */,
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
+ 4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */,
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */,
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */,
4C3EA66528FF5F6800C48A62 /* mem.c in Sources */,
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -66,6 +66,8 @@ struct ContentView: View {
@State var profile_open: Bool = false
@State var thread_open: Bool = false
@State var search_open: Bool = false
+ @State var wallet_open: Bool = false
+ @State var active_nwc: WalletConnectURL? = nil
@State var muting: String? = nil
@State var confirm_mute: Bool = false
@State var user_muted_confirm: Bool = false
@@ -131,6 +133,7 @@ struct ContentView: View {
profile_open = false
thread_open = false
search_open = false
+ wallet_open = false
isSideBarOpened = false
}
@@ -141,6 +144,9 @@ struct ContentView: View {
func MainContent(damus: DamusState) -> some View {
VStack {
+ NavigationLink(destination: WalletView(model: damus_state!.wallet), isActive: $wallet_open) {
+ EmptyView()
+ }
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
EmptyView()
}
@@ -235,6 +241,11 @@ struct ContentView: View {
self.thread_open = true
}
+ func open_wallet(nwc: WalletConnectURL) {
+ self.damus_state!.wallet.new(nwc)
+ self.wallet_open = true
+ }
+
func open_profile(id: String) {
self.active_profile = id
self.profile_open = true
@@ -320,29 +331,17 @@ struct ContentView: View {
}
}
.onOpenURL { url in
- guard let link = decode_nostr_uri(url.absoluteString) else {
- return
- }
-
- switch link {
- case .ref(let ref):
- if ref.key == "p" {
- active_profile = ref.ref_id
- profile_open = true
- } else if ref.key == "e" {
- find_event(state: damus_state!, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in
- if let ev {
- open_event(ev: ev)
- }
- }
+ on_open_url(state: damus_state!, url: url) { res in
+ guard let res else {
+ return
}
- case .filter(let filt):
- active_search = filt
- search_open = true
- break
- // TODO: handle filter searches?
+
+ switch res {
+ case .filter(let filt): self.open_search(filt: filt)
+ case .profile(let id): self.open_profile(id: id)
+ case .event(let ev): self.open_event(ev: ev)
+ case .wallet_connect(let nwc): self.open_wallet(nwc: nwc)}
}
-
}
.onReceive(handle_notify(.compose)) { notif in
let action = notif.object as! PostAction
@@ -589,7 +588,8 @@ struct ContentView: View {
postbox: PostBox(pool: pool),
bootstrap_relays: bootstrap_relays,
replies: ReplyCounter(our_pubkey: pubkey),
- muted_threads: MutedThreadsManager(keypair: keypair)
+ muted_threads: MutedThreadsManager(keypair: keypair),
+ wallet: WalletModel(settings: settings)
)
home.damus_state = self.damus_state!
@@ -839,3 +839,40 @@ func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: Ev
return false
}
}
+
+
+enum OpenResult {
+ case profile(String)
+ case filter(NostrFilter)
+ case event(NostrEvent)
+ case wallet_connect(WalletConnectURL)
+}
+
+func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) -> Void) {
+ if let nwc = WalletConnectURL(str: url.absoluteString) {
+ result(.wallet_connect(nwc))
+ return
+ }
+
+ guard let link = decode_nostr_uri(url.absoluteString) else {
+ result(nil)
+ return
+ }
+
+ switch link {
+ case .ref(let ref):
+ if ref.key == "p" {
+ result(.profile(ref.ref_id))
+ } else if ref.key == "e" {
+ find_event(state: state, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in
+ if let ev {
+ result(.event(ev))
+ }
+ }
+ }
+ case .filter(let filt):
+ result(.filter(filt))
+ break
+ // TODO: handle filter searches?
+ }
+}
diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift
@@ -29,6 +29,7 @@ struct DamusState {
let bootstrap_relays: [String]
let replies: ReplyCounter
let muted_threads: MutedThreadsManager
+ let wallet: WalletModel
@discardableResult
func add_zap(zap: Zap) -> Bool {
@@ -47,5 +48,5 @@ struct DamusState {
}
static var empty: DamusState {
- return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil))) }
+ return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil)), wallet: WalletModel()) }
}
diff --git a/damus/Views/SideMenuView.swift b/damus/Views/SideMenuView.swift
@@ -48,11 +48,17 @@ struct SideMenuView: View {
navLabel(title: NSLocalizedString("Profile", comment: "Sidebar menu label for Profile view."), systemImage: "person")
}
- /*
- NavigationLink(destination: EmptyView()) {
- navLabel(title: NSLocalizedString("Wallet", comment: "Sidebar menu label for Wallet view."), systemImage: "bolt")
+ NavigationLink(destination: WalletView(model: damus_state.wallet)) {
+ HStack {
+ Image("wallet")
+ .tint(DamusColors.adaptableBlack)
+
+ Text(NSLocalizedString("Wallet", comment: "Sidebar menu label for Wallet view."))
+ .font(.title2)
+ .foregroundColor(textColor())
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
}
- */
NavigationLink(destination: MutelistView(damus_state: damus_state, users: get_mutelist_users(damus_state.contacts.mutelist) )) {
navLabel(title: NSLocalizedString("Muted", comment: "Sidebar menu label for muted users view."), systemImage: "exclamationmark.octagon")
diff --git a/damus/Views/Wallet/ConnectWalletView.swift b/damus/Views/Wallet/ConnectWalletView.swift
@@ -0,0 +1,98 @@
+//
+// ConnectWalletView.swift
+// damus
+//
+// Created by William Casarin on 2023-05-05.
+//
+
+import SwiftUI
+
+struct ConnectWalletView: View {
+ @Environment(\.openURL) private var openURL
+ @ObservedObject var model: WalletModel
+
+ @State var scanning: Bool = false
+ @State var error: String? = nil
+ @State var wallet_scan_result: WalletScanResult = .scanning
+
+ var body: some View {
+ MainContent
+ .navigationTitle("Attach a Wallet")
+ .navigationBarTitleDisplayMode(.large)
+ .padding()
+ .onChange(of: wallet_scan_result) { res in
+ scanning = false
+
+ switch res {
+ case .success(let url):
+ error = nil
+ self.model.new(url)
+
+ case .failed:
+ error = "Invalid nostr wallet connection string"
+
+ case .scanning:
+ error = nil
+ }
+ }
+ }
+
+ func AreYouSure(nwc: WalletConnectURL) -> some View {
+ VStack {
+ Text("Are you sure you want to attach this wallet?")
+ .font(.title)
+
+ Text(nwc.relay.id)
+ .font(.body)
+ .foregroundColor(.gray)
+
+ BigButton("Attach") {
+ model.connect(nwc)
+ }
+
+ BigButton("Cancel") {
+ model.cancel()
+ }
+ }
+ }
+
+ var ConnectWallet: some View {
+ VStack {
+ NavigationLink(destination: WalletScannerView(result: $wallet_scan_result), isActive: $scanning) {
+ EmptyView()
+ }
+
+ AlbyButton() {
+ openURL(URL(string:"https://nwc.getalby.com/apps/new?c=Damus")!)
+ }
+
+ BigButton("Attach Wallet") {
+ scanning = true
+ }
+
+ if let err = self.error {
+ Text(err)
+ .foregroundColor(.red)
+ }
+ }
+ }
+
+ var MainContent: some View {
+ Group {
+ switch model.connect_state {
+ case .new(let nwc):
+ AreYouSure(nwc: nwc)
+ case .existing:
+ Text("Shouldn't happen")
+ case .none:
+ ConnectWallet
+ }
+ }
+ }
+}
+
+struct ConnectWalletView_Previews: PreviewProvider {
+ static var previews: some View {
+ ConnectWalletView(model: WalletModel())
+ }
+}
diff --git a/damus/Views/Wallet/NWCScannerView.swift b/damus/Views/Wallet/NWCScannerView.swift
@@ -0,0 +1,77 @@
+//
+// QRScannerView.swift
+// damus
+//
+// Created by William Casarin on 2023-05-09.
+//
+
+import SwiftUI
+
+enum WalletScanResult: Equatable {
+ static func == (lhs: WalletScanResult, rhs: WalletScanResult) -> Bool {
+ switch lhs {
+ case .success(let a):
+ switch rhs {
+ case .success(let b):
+ return a == b
+ case .failed:
+ return false
+ case .scanning:
+ return false
+ }
+ case .failed:
+ switch rhs {
+ case .success:
+ return false
+ case .failed:
+ return true
+ case .scanning:
+ return false
+ }
+ case .scanning:
+ switch rhs {
+ case .success:
+ return false
+ case .failed:
+ return false
+ case .scanning:
+ return true
+ }
+ }
+ }
+
+ case success(WalletConnectURL)
+ case failed
+ case scanning
+}
+
+struct WalletScannerView: View {
+ @Binding var result: WalletScanResult
+
+ @Environment(\.dismiss) var dismiss
+
+ var body: some View {
+ CodeScannerView(codeTypes: [.qr]) { res in
+ switch res {
+ case .success(let success):
+ guard let url = WalletConnectURL(str: success.string) else {
+ result = .failed
+ return
+ }
+
+ result = .success(url)
+ case .failure:
+ result = .failed
+ }
+
+ dismiss()
+ }
+ }
+}
+
+struct QRScannerView_Previews: PreviewProvider {
+ @State static var result: WalletScanResult = .scanning
+ static var previews: some View {
+ WalletScannerView(result: $result)
+ }
+}
diff --git a/damus/Views/Wallet/WalletView.swift b/damus/Views/Wallet/WalletView.swift
@@ -0,0 +1,40 @@
+//
+// WalletView.swift
+// damus
+//
+// Created by William Casarin on 2023-05-05.
+//
+
+import SwiftUI
+
+struct WalletView: View {
+ @ObservedObject var model: WalletModel
+
+ func MainWalletView(nwc: WalletConnectURL) -> some View {
+ VStack {
+ Text("\(nwc.relay.id)")
+
+ BigButton("Disconnect Wallet") {
+ self.model.disconnect()
+ }
+ }
+ .padding()
+ }
+
+ var body: some View {
+ switch model.connect_state {
+ case .new:
+ ConnectWalletView(model: model)
+ case .none:
+ ConnectWalletView(model: model)
+ case .existing(let nwc):
+ MainWalletView(nwc: nwc)
+ }
+ }
+}
+
+struct WalletView_Previews: PreviewProvider {
+ static var previews: some View {
+ WalletView(model: WalletModel())
+ }
+}