commit 71f7ea47df67f0a5ff8f85d5cfd72a610fe51242
parent 64b1a57918d0aeed621185103c69414827d3f637
Author: William Casarin <jb55@jb55.com>
Date: Sat, 25 Feb 2023 12:10:37 -0800
Customized Zaps
Changelog-Added: Customized zaps
Diffstat:
13 files changed, 458 insertions(+), 86 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -134,6 +134,8 @@
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */; };
+ 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
+ 4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
@@ -470,6 +472,8 @@
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatroomMetadata.swift; sourceTree = "<group>"; };
+ 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
+ 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeAnonPfpView.swift; sourceTree = "<group>"; };
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; };
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
@@ -909,6 +913,7 @@
children = (
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
+ 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
);
path = Profile;
sourceTree = "<group>";
@@ -1066,6 +1071,7 @@
isa = PBXGroup;
children = (
4CE879572996C45300F758CC /* ZapsView.swift */,
+ 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */,
);
path = Zaps;
sourceTree = "<group>";
@@ -1304,6 +1310,7 @@
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
+ 4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
@@ -1398,6 +1405,7 @@
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
+ 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
diff --git a/damus/Components/ZapButton.swift b/damus/Components/ZapButton.swift
@@ -7,6 +7,22 @@
import SwiftUI
+enum ZappingEventType {
+ case failed(ZappingError)
+ case got_zap_invoice(String)
+}
+
+enum ZappingError {
+ case fetching_invoice
+ case bad_lnurl
+}
+
+struct ZappingEvent {
+ let is_custom: Bool
+ let type: ZappingEventType
+ let event: NostrEvent
+}
+
struct ZapButton: View {
let damus_state: DamusState
let event: NostrEvent
@@ -19,61 +35,8 @@ struct ZapButton: View {
@State var slider_value: Double = 0.0
@State var slider_visible: Bool = false
@State var showing_select_wallet: Bool = false
-
- func send_zap() {
- guard let privkey = damus_state.keypair.privkey else {
- return
- }
-
- // Only take the first 10 because reasons
- let relays = Array(damus_state.pool.descriptors.prefix(10))
- let target = ZapTarget.note(id: event.id, author: event.pubkey)
- // TODO: gather comment?
- let content = ""
- let zapreq = make_zap_request_event(pubkey: damus_state.pubkey, privkey: privkey, content: content, relays: relays, target: target)
-
- zapping = true
-
- Task {
- var mpayreq = damus_state.lnurls.lookup(target.pubkey)
- if mpayreq == nil {
- mpayreq = await fetch_static_payreq(lnurl)
- }
-
- guard let payreq = mpayreq else {
- // TODO: show error
- DispatchQueue.main.async {
- zapping = false
- }
- return
- }
-
- DispatchQueue.main.async {
- damus_state.lnurls.endpoints[target.pubkey] = payreq
- }
-
- let zap_amount = get_default_zap_amount(pubkey: damus_state.pubkey) ?? 1000
- guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount) else {
- DispatchQueue.main.async {
- zapping = false
- }
- return
- }
-
- DispatchQueue.main.async {
- zapping = false
-
- if should_show_wallet_selector(damus_state.pubkey) {
- self.invoice = inv
- self.showing_select_wallet = true
- } else {
- open_with_wallet(wallet: get_default_wallet(damus_state.pubkey).model, invoice: inv)
- }
- }
- }
-
- //damus_state.pool.send(.event(zapreq))
- }
+ @State var showing_zap_customizer: Bool = false
+ @State var is_charging: Bool = false
var zap_img: String {
if bar.zapped {
@@ -92,6 +55,10 @@ struct ZapButton: View {
return Color.orange
}
+ if is_charging {
+ return Color.yellow
+ }
+
if !zapping {
return nil
}
@@ -101,14 +68,24 @@ struct ZapButton: View {
var body: some View {
HStack(spacing: 4) {
- EventActionButton(img: zap_img, col: zap_color) {
- if bar.zapped {
- //notify(.delete, bar.our_tip)
- } else if !zapping {
- send_zap()
+ Image(systemName: zap_img)
+ .foregroundColor(zap_color == nil ? Color.gray : zap_color!)
+ .font(.footnote.weight(.medium))
+ .onTapGesture {
+ if bar.zapped {
+ //notify(.delete, bar.our_tip)
+ } else if !zapping {
+ self.showing_zap_customizer = true
+ //send_zap(damus_state: damus_state, event: event, lnurl: lnurl, is_custom: false)
+ //self.zapping = true
+ }
}
- }
- .accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
+ .onLongPressGesture(minimumDuration: 0, pressing: { is_charing in
+ self.is_charging = is_charging
+ }, perform: {
+ self.showing_zap_customizer = true
+ })
+ .accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
if bar.zap_total > 0 {
Text(verbatim: format_msats_abbrev(bar.zap_total))
@@ -116,9 +93,37 @@ struct ZapButton: View {
.foregroundColor(bar.zapped ? Color.orange : Color.gray)
}
}
+ .sheet(isPresented: $showing_zap_customizer) {
+ CustomizeZapView(state: damus_state, event: event, lnurl: lnurl)
+ }
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
SelectWalletView(showingSelectWallet: $showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: invoice)
}
+ .onReceive(handle_notify(.zapping)) { notif in
+ let zap_ev = notif.object as! ZappingEvent
+
+ guard zap_ev.event.id == self.event.id else {
+ return
+ }
+
+ guard !zap_ev.is_custom else {
+ return
+ }
+
+ switch zap_ev.type {
+ case .failed:
+ break
+ case .got_zap_invoice(let inv):
+ if should_show_wallet_selector(damus_state.pubkey) {
+ self.invoice = inv
+ self.showing_select_wallet = true
+ } else {
+ open_with_wallet(wallet: get_default_wallet(damus_state.pubkey).model, invoice: inv)
+ }
+ }
+
+ self.zapping = false
+ }
}
}
@@ -130,3 +135,55 @@ struct ZapButton_Previews: PreviewProvider {
}
}
+
+
+func send_zap(damus_state: DamusState, event: NostrEvent, lnurl: String, is_custom: Bool, comment: String?, amount_sats: Int?, zap_type: ZapType) {
+ guard let privkey = damus_state.keypair.privkey else {
+ return
+ }
+
+ // Only take the first 10 because reasons
+ let relays = Array(damus_state.pool.descriptors.prefix(10))
+ let target = ZapTarget.note(id: event.id, author: event.pubkey)
+ let content = comment ?? ""
+ let zapreq = make_zap_request_event(pubkey: damus_state.pubkey, privkey: privkey, content: content, relays: relays, target: target, is_anon: zap_type == .anon)
+
+ Task {
+ var mpayreq = damus_state.lnurls.lookup(target.pubkey)
+ if mpayreq == nil {
+ mpayreq = await fetch_static_payreq(lnurl)
+ }
+
+ guard let payreq = mpayreq else {
+ // TODO: show error
+ DispatchQueue.main.async {
+ let typ = ZappingEventType.failed(.bad_lnurl)
+ let ev = ZappingEvent(is_custom: is_custom, type: typ, event: event)
+ notify(.zapping, ev)
+ }
+ return
+ }
+
+ DispatchQueue.main.async {
+ damus_state.lnurls.endpoints[target.pubkey] = payreq
+ }
+
+ let zap_amount = amount_sats ?? get_default_zap_amount(pubkey: damus_state.pubkey) ?? 1000
+
+ guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount, zap_type: zap_type, comment: comment) else {
+ DispatchQueue.main.async {
+ let typ = ZappingEventType.failed(.fetching_invoice)
+ let ev = ZappingEvent(is_custom: is_custom, type: typ, event: event)
+ notify(.zapping, ev)
+ }
+ return
+ }
+
+ DispatchQueue.main.async {
+ let ev = ZappingEvent(is_custom: is_custom, type: .got_zap_invoice(inv), event: event)
+ notify(.zapping, ev)
+ }
+ }
+
+ return
+}
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -125,6 +125,8 @@ class HomeModel: ObservableObject {
handle_channel_meta(ev)
case .zap:
handle_zap_event(ev)
+ case .zap_request:
+ break
}
}
diff --git a/damus/Nostr/Nostr.swift b/damus/Nostr/Nostr.swift
@@ -141,6 +141,9 @@ struct Profile: Codable {
}
static func displayName(profile: Profile?, pubkey: String) -> String {
+ if pubkey == "anon" {
+ return "Anonymous"
+ }
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey
return profile?.name ?? abbrev_pubkey(pk)
}
diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift
@@ -577,14 +577,25 @@ func zap_target_to_tags(_ target: ZapTarget) -> [[String]] {
}
}
-func make_zap_request_event(pubkey: String, privkey: String, content: String, relays: [RelayDescriptor], target: ZapTarget) -> NostrEvent {
+func make_zap_request_event(pubkey: String, privkey: String, content: String, relays: [RelayDescriptor], target: ZapTarget, is_anon: Bool) -> NostrEvent {
var tags = zap_target_to_tags(target)
var relay_tag = ["relays"]
relay_tag.append(contentsOf: relays.map { $0.url.absoluteString })
tags.append(relay_tag)
- let ev = NostrEvent(content: content, pubkey: pubkey, kind: 9734, tags: tags)
+
+ var priv = privkey
+ var pub = pubkey
+
+ if is_anon {
+ tags.append(["anon"])
+ let kp = generate_new_keypair()
+ pub = kp.pubkey
+ priv = kp.privkey!
+ }
+
+ let ev = NostrEvent(content: content, pubkey: pub, kind: 9734, tags: tags)
ev.id = calculate_event_id(ev: ev)
- ev.sig = sign_event(privkey: privkey, ev: ev)
+ ev.sig = sign_event(privkey: priv, ev: ev)
return ev
}
diff --git a/damus/Nostr/NostrKind.swift b/damus/Nostr/NostrKind.swift
@@ -21,4 +21,5 @@ enum NostrKind: Int {
case chat = 42
case list = 30000
case zap = 9735
+ case zap_request = 9734
}
diff --git a/damus/Util/LNUrlPayRequest.swift b/damus/Util/LNUrlPayRequest.swift
@@ -9,8 +9,10 @@ import Foundation
struct LNUrlPayRequest: Decodable {
let allowsNostr: Bool?
+ let commentAllowed: Int?
let nostrPubkey: String?
+ let metadata: String?
let minSendable: Int64?
let maxSendable: Int64?
let status: String?
diff --git a/damus/Util/Notifications.swift b/damus/Util/Notifications.swift
@@ -104,6 +104,9 @@ extension Notification.Name {
static var update_bookmarks: Notification.Name {
return Notification.Name("update_bookmarks")
}
+ static var zapping: Notification.Name {
+ return Notification.Name("zapping")
+ }
}
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
diff --git a/damus/Util/Zap.swift b/damus/Util/Zap.swift
@@ -285,7 +285,7 @@ func fetch_static_payreq(_ lnurl: String) async -> LNUrlPayRequest? {
return endpoint
}
-func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int) async -> String? {
+func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int, zap_type: ZapType, comment: String?) async -> String? {
guard var base_url = payreq.callback.flatMap({ URLComponents(string: $0) }) else {
return nil
}
@@ -295,12 +295,18 @@ func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int)
var query = [URLQueryItem(name: "amount", value: "\(amount)")]
- if zappable {
+ if zappable && zap_type != .non_zap {
if let json = encode_json(zapreq) {
print("zapreq json: \(json)")
query.append(URLQueryItem(name: "nostr", value: json))
}
}
+
+ // add a lud12 comment as well if we have it
+ if let comment, let limit = payreq.commentAllowed, limit != 0 {
+ let limited_comment = String(comment.prefix(limit))
+ query.append(URLQueryItem(name: "comment", value: limited_comment))
+ }
base_url.queryItems = query
diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift
@@ -129,26 +129,14 @@ struct ConfigView: View {
}
}
-
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
TextField(String("1000"), text: $default_zap_amount)
.keyboardType(.numberPad)
.onReceive(Just(default_zap_amount)) { newValue in
- let filtered = newValue.filter { Set("0123456789").contains($0) }
-
- if filtered != newValue {
- default_zap_amount = filtered
- }
-
- if filtered == "" {
- set_default_zap_amount(pubkey: state.pubkey, amount: 1000)
- return
+
+ if let parsed = handle_string_amount(new_value: newValue) {
+ self.default_zap_amount = String(parsed)
}
-
- guard let amt = Int(filtered) else {
- return
- }
- set_default_zap_amount(pubkey: state.pubkey, amount: amt)
}
}
@@ -346,3 +334,18 @@ struct ConfigView_Previews: PreviewProvider {
}
}
}
+
+
+func handle_string_amount(new_value: String) -> Int? {
+ let filtered = new_value.filter { Set("0123456789").contains($0) }
+
+ if filtered == "" {
+ return nil
+ }
+
+ guard let amt = Int(filtered) else {
+ return nil
+ }
+
+ return amt
+}
diff --git a/damus/Views/Events/TextEvent.swift b/damus/Views/Events/TextEvent.swift
@@ -18,17 +18,17 @@ struct TextEvent: View {
HStack(alignment: .top) {
let profile = damus.profiles.lookup(id: pubkey)
+ let is_anon = event_is_anonymous(ev: event)
VStack {
- NavigationLink(destination: ProfileView(damus_state: damus, pubkey: pubkey)) {
- ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
- }
+ MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
Spacer()
}
VStack(alignment: .leading) {
HStack(alignment: .center) {
- EventProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
+ let pk = is_anon ? "anon" : pubkey
+ EventProfileName(pubkey: pk, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
Text(verbatim: "\(format_relative_time(event.created_at))")
.foregroundColor(.gray)
@@ -65,3 +65,18 @@ struct TextEvent_Previews: PreviewProvider {
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", has_action_bar: true, booster_pubkey: nil)
}
}
+
+func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
+ for t in ev.tags {
+ if t.count >= 1 && t[0] == tag {
+ return true
+ }
+ }
+
+ return false
+}
+
+
+func event_is_anonymous(ev: NostrEvent) -> Bool {
+ return ev.known_kind == .zap_request && event_has_tag(ev: ev, tag: "anon")
+}
diff --git a/damus/Views/Profile/MaybeAnonPfpView.swift b/damus/Views/Profile/MaybeAnonPfpView.swift
@@ -0,0 +1,46 @@
+//
+// MaybeAnonPfpView.swift
+// damus
+//
+// Created by William Casarin on 2023-02-26.
+//
+
+import SwiftUI
+
+struct MaybeAnonPfpView: View {
+ let state: DamusState
+ let is_anon: Bool
+ let pubkey: String
+
+ init(state: DamusState, event: NostrEvent, pubkey: String) {
+ self.state = state
+ self.is_anon = event_is_anonymous(ev: event)
+ self.pubkey = pubkey
+ }
+
+ init(state: DamusState, is_anon: Bool, pubkey: String) {
+ self.state = state
+ self.is_anon = is_anon
+ self.pubkey = pubkey
+ }
+
+ var body: some View {
+ Group {
+ if is_anon {
+ Image(systemName: "person.fill.questionmark")
+ .font(.largeTitle)
+ .frame(width: PFP_SIZE, height: PFP_SIZE)
+ } else {
+ NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
+ ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: state.profiles)
+ }
+ }
+ }
+ }
+}
+
+struct MaybeAnonPfpView_Previews: PreviewProvider {
+ static var previews: some View {
+ MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon")
+ }
+}
diff --git a/damus/Views/Zaps/CustomizeZapView.swift b/damus/Views/Zaps/CustomizeZapView.swift
@@ -0,0 +1,215 @@
+//
+// CustomizeZapView.swift
+// damus
+//
+// Created by William Casarin on 2023-02-25.
+//
+
+import SwiftUI
+import Combine
+
+enum ZapType {
+ case pub
+ case anon
+ case non_zap
+}
+
+struct ZapAmountItem: Identifiable, Hashable {
+ let amount: Int
+ let icon: String
+
+ var id: String {
+ return icon
+ }
+}
+
+func get_default_zap_amount_item(_ pubkey: String) -> ZapAmountItem {
+ let def = get_default_zap_amount(pubkey: pubkey) ?? 1000
+ return ZapAmountItem(amount: def, icon: "🤙")
+}
+
+func get_zap_amount_items(pubkey: String) -> [ZapAmountItem] {
+ let def_item = get_default_zap_amount_item(pubkey)
+ var entries = [
+ ZapAmountItem(amount: 500, icon: "🙂"),
+ ZapAmountItem(amount: 5000, icon: "💜"),
+ ZapAmountItem(amount: 10_000, icon: "😍"),
+ ZapAmountItem(amount: 20_000, icon: "🤩"),
+ ZapAmountItem(amount: 50_000, icon: "🔥"),
+ ZapAmountItem(amount: 100_000, icon: "🚀"),
+ ZapAmountItem(amount: 1_000_000, icon: "🤯"),
+ ]
+ entries.append(def_item)
+
+ entries.sort { $0.amount < $1.amount }
+ return entries
+}
+
+struct CustomizeZapView: View {
+ let state: DamusState
+ let event: NostrEvent
+ let lnurl: String
+ @State var comment: String
+ @State var custom_amount: String
+ @State var custom_amount_sats: Int?
+ @State var selected_amount: ZapAmountItem
+ @State var zap_type: ZapType
+ @State var invoice: String
+ @State var error: String?
+ @State var showing_wallet_selector: Bool
+ @State var zapping: Bool
+
+ let zap_amounts: [ZapAmountItem]
+
+ @Environment(\.dismiss) var dismiss
+
+ init(state: DamusState, event: NostrEvent, lnurl: String) {
+ self._comment = State(initialValue: "")
+ self.event = event
+ self.zap_amounts = get_zap_amount_items(pubkey: state.pubkey)
+ self._error = State(initialValue: nil)
+ self._invoice = State(initialValue: "")
+ self._showing_wallet_selector = State(initialValue: false)
+ self._custom_amount = State(initialValue: "")
+ self._zap_type = State(initialValue: .pub)
+ let selected = get_default_zap_amount_item(state.pubkey)
+ self._selected_amount = State(initialValue: selected)
+ self._custom_amount_sats = State(initialValue: nil)
+ self._zapping = State(initialValue: false)
+ self.lnurl = lnurl
+ self.state = state
+ }
+
+ var ZapTypePicker: some View {
+ Picker("Zap Type", selection: $zap_type) {
+ Text("Public").tag(ZapType.pub)
+ Text("Anonymous").tag(ZapType.anon)
+ Text("Non-Zap").tag(ZapType.non_zap)
+ }
+ .pickerStyle(.segmented)
+ }
+
+ var AmountPicker: some View {
+ Picker("Zap Amount", selection: $selected_amount) {
+ ForEach(zap_amounts) { entry in
+ let fmt = format_msats_abbrev(Int64(entry.amount) * 1000)
+ HStack(alignment: .firstTextBaseline) {
+ Text("\(entry.icon)")
+ .frame(width: 30)
+ Text("\(fmt)")
+ .frame(width: 50)
+ }
+ .tag(entry)
+ }
+ }
+ .pickerStyle(.wheel)
+ }
+
+ func receive_zap(notif: Notification) {
+ let zap_ev = notif.object as! ZappingEvent
+ guard zap_ev.is_custom else {
+ return
+ }
+ guard zap_ev.event.id == event.id else {
+ return
+ }
+
+ self.zapping = false
+
+ switch zap_ev.type {
+ case .failed(let err):
+ switch err {
+ case .fetching_invoice:
+ self.error = "Error fetching lightning invoice"
+ case .bad_lnurl:
+ self.error = "Invalid lightning address"
+ }
+ break
+ case .got_zap_invoice(let inv):
+ if should_show_wallet_selector(state.pubkey) {
+ self.invoice = inv
+ self.showing_wallet_selector = true
+ } else {
+ open_with_wallet(wallet: get_default_wallet(state.pubkey).model, invoice: inv)
+ self.showing_wallet_selector = false
+ dismiss()
+ }
+ }
+
+
+}
+
+ var body: some View {
+ MainContent
+ .sheet(isPresented: $showing_wallet_selector) {
+ SelectWalletView(showingSelectWallet: $showing_wallet_selector, our_pubkey: state.pubkey, invoice: invoice)
+ }
+ .onReceive(handle_notify(.zapping)) { notif in
+ receive_zap(notif: notif)
+ }
+ .ignoresSafeArea()
+ }
+
+ var MainContent: some View {
+ VStack(alignment: .leading) {
+ Form {
+ Section(content: {
+ AmountPicker
+ }, header: {
+ Text("Zap Amount in sats")
+ })
+
+ Section(content: {
+ TextField("100000", text: $custom_amount)
+ .keyboardType(.numberPad)
+ .onReceive(Just(custom_amount)) { newValue in
+
+ if let parsed = handle_string_amount(new_value: newValue) {
+ self.custom_amount = String(parsed)
+ self.custom_amount_sats = parsed
+ }
+ }
+ }, header: {
+ Text("Custom Zap Amount")
+ })
+ .dismissKeyboardOnTap()
+
+ Section(content: {
+ TextField("Awesome post!", text: $comment)
+ }, header: {
+ Text("Comment")
+ })
+ .dismissKeyboardOnTap()
+
+ Section(content: {
+ ZapTypePicker
+ }, header: {
+ Text("Zap Type")
+ })
+
+ if zapping {
+ Text("Zapping...")
+ } else {
+ Button("Zap") {
+ let amount = custom_amount_sats ?? selected_amount.amount
+ send_zap(damus_state: state, event: event, lnurl: lnurl, is_custom: true, comment: comment, amount_sats: amount, zap_type: zap_type)
+ self.zapping = true
+ }
+ .zIndex(16)
+ }
+
+ if let error {
+ Text(error)
+ .foregroundColor(.red)
+ }
+ }
+ }
+ }
+}
+
+struct CustomizeZapView_Previews: PreviewProvider {
+ static var previews: some View {
+ CustomizeZapView(state: test_damus_state(), event: test_event, lnurl: "")
+ .frame(width: 400, height: 600)
+ }
+}