ui: add Nostr Wallet Connect views

This commit is contained in:
William Casarin
2023-05-09 18:50:08 -07:00
parent fe3d976cdb
commit 370a5feb4e
7 changed files with 310 additions and 27 deletions

View File

@@ -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 */,

View File

@@ -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?
}
}

View File

@@ -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()) }
}

View File

@@ -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")

View File

@@ -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())
}
}

View File

@@ -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)
}
}

View File

@@ -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())
}
}