Customized Zaps
Changelog-Added: Customized zaps
This commit is contained in:
@@ -134,6 +134,8 @@
|
|||||||
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
|
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
|
||||||
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
|
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
|
||||||
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C99737A28C92A9200E53835 /* ChatroomMetadata.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 */; };
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||||
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
|
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
|
||||||
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
|
||||||
@@ -909,6 +913,7 @@
|
|||||||
children = (
|
children = (
|
||||||
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
|
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
|
||||||
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
|
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
|
||||||
|
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
|
||||||
);
|
);
|
||||||
path = Profile;
|
path = Profile;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1066,6 +1071,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4CE879572996C45300F758CC /* ZapsView.swift */,
|
4CE879572996C45300F758CC /* ZapsView.swift */,
|
||||||
|
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */,
|
||||||
);
|
);
|
||||||
path = Zaps;
|
path = Zaps;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1304,6 +1310,7 @@
|
|||||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
||||||
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
||||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||||
|
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
||||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
|
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
|
||||||
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
|
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
|
||||||
@@ -1398,6 +1405,7 @@
|
|||||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
||||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
||||||
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
|
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
|
||||||
|
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
|
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
|
||||||
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
|
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
|
||||||
|
|||||||
@@ -7,6 +7,22 @@
|
|||||||
|
|
||||||
import SwiftUI
|
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 {
|
struct ZapButton: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
@@ -19,61 +35,8 @@ struct ZapButton: View {
|
|||||||
@State var slider_value: Double = 0.0
|
@State var slider_value: Double = 0.0
|
||||||
@State var slider_visible: Bool = false
|
@State var slider_visible: Bool = false
|
||||||
@State var showing_select_wallet: Bool = false
|
@State var showing_select_wallet: Bool = false
|
||||||
|
@State var showing_zap_customizer: Bool = false
|
||||||
func send_zap() {
|
@State var is_charging: Bool = false
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
var zap_img: String {
|
var zap_img: String {
|
||||||
if bar.zapped {
|
if bar.zapped {
|
||||||
@@ -92,6 +55,10 @@ struct ZapButton: View {
|
|||||||
return Color.orange
|
return Color.orange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_charging {
|
||||||
|
return Color.yellow
|
||||||
|
}
|
||||||
|
|
||||||
if !zapping {
|
if !zapping {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -101,13 +68,23 @@ struct ZapButton: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
EventActionButton(img: zap_img, col: zap_color) {
|
Image(systemName: zap_img)
|
||||||
|
.foregroundColor(zap_color == nil ? Color.gray : zap_color!)
|
||||||
|
.font(.footnote.weight(.medium))
|
||||||
|
.onTapGesture {
|
||||||
if bar.zapped {
|
if bar.zapped {
|
||||||
//notify(.delete, bar.our_tip)
|
//notify(.delete, bar.our_tip)
|
||||||
} else if !zapping {
|
} else if !zapping {
|
||||||
send_zap()
|
self.showing_zap_customizer = true
|
||||||
|
//send_zap(damus_state: damus_state, event: event, lnurl: lnurl, is_custom: false)
|
||||||
|
//self.zapping = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.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"))
|
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
|
||||||
|
|
||||||
if bar.zap_total > 0 {
|
if bar.zap_total > 0 {
|
||||||
@@ -116,9 +93,37 @@ struct ZapButton: View {
|
|||||||
.foregroundColor(bar.zapped ? Color.orange : Color.gray)
|
.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}) {
|
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
|
||||||
SelectWalletView(showingSelectWallet: $showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: invoice)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ class HomeModel: ObservableObject {
|
|||||||
handle_channel_meta(ev)
|
handle_channel_meta(ev)
|
||||||
case .zap:
|
case .zap:
|
||||||
handle_zap_event(ev)
|
handle_zap_event(ev)
|
||||||
|
case .zap_request:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ struct Profile: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func displayName(profile: Profile?, pubkey: String) -> String {
|
static func displayName(profile: Profile?, pubkey: String) -> String {
|
||||||
|
if pubkey == "anon" {
|
||||||
|
return "Anonymous"
|
||||||
|
}
|
||||||
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey
|
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey
|
||||||
return profile?.name ?? abbrev_pubkey(pk)
|
return profile?.name ?? abbrev_pubkey(pk)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 tags = zap_target_to_tags(target)
|
||||||
var relay_tag = ["relays"]
|
var relay_tag = ["relays"]
|
||||||
relay_tag.append(contentsOf: relays.map { $0.url.absoluteString })
|
relay_tag.append(contentsOf: relays.map { $0.url.absoluteString })
|
||||||
tags.append(relay_tag)
|
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.id = calculate_event_id(ev: ev)
|
||||||
ev.sig = sign_event(privkey: privkey, ev: ev)
|
ev.sig = sign_event(privkey: priv, ev: ev)
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,5 @@ enum NostrKind: Int {
|
|||||||
case chat = 42
|
case chat = 42
|
||||||
case list = 30000
|
case list = 30000
|
||||||
case zap = 9735
|
case zap = 9735
|
||||||
|
case zap_request = 9734
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import Foundation
|
|||||||
|
|
||||||
struct LNUrlPayRequest: Decodable {
|
struct LNUrlPayRequest: Decodable {
|
||||||
let allowsNostr: Bool?
|
let allowsNostr: Bool?
|
||||||
|
let commentAllowed: Int?
|
||||||
let nostrPubkey: String?
|
let nostrPubkey: String?
|
||||||
|
|
||||||
|
let metadata: String?
|
||||||
let minSendable: Int64?
|
let minSendable: Int64?
|
||||||
let maxSendable: Int64?
|
let maxSendable: Int64?
|
||||||
let status: String?
|
let status: String?
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ extension Notification.Name {
|
|||||||
static var update_bookmarks: Notification.Name {
|
static var update_bookmarks: Notification.Name {
|
||||||
return Notification.Name("update_bookmarks")
|
return Notification.Name("update_bookmarks")
|
||||||
}
|
}
|
||||||
|
static var zapping: Notification.Name {
|
||||||
|
return Notification.Name("zapping")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
|
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ func fetch_static_payreq(_ lnurl: String) async -> LNUrlPayRequest? {
|
|||||||
return endpoint
|
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 {
|
guard var base_url = payreq.callback.flatMap({ URLComponents(string: $0) }) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -295,13 +295,19 @@ func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int)
|
|||||||
|
|
||||||
var query = [URLQueryItem(name: "amount", value: "\(amount)")]
|
var query = [URLQueryItem(name: "amount", value: "\(amount)")]
|
||||||
|
|
||||||
if zappable {
|
if zappable && zap_type != .non_zap {
|
||||||
if let json = encode_json(zapreq) {
|
if let json = encode_json(zapreq) {
|
||||||
print("zapreq json: \(json)")
|
print("zapreq json: \(json)")
|
||||||
query.append(URLQueryItem(name: "nostr", value: 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
|
base_url.queryItems = query
|
||||||
|
|
||||||
guard let url = base_url.url else {
|
guard let url = base_url.url else {
|
||||||
|
|||||||
@@ -129,26 +129,14 @@ struct ConfigView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
|
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
|
||||||
TextField(String("1000"), text: $default_zap_amount)
|
TextField(String("1000"), text: $default_zap_amount)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
.onReceive(Just(default_zap_amount)) { newValue in
|
.onReceive(Just(default_zap_amount)) { newValue in
|
||||||
let filtered = newValue.filter { Set("0123456789").contains($0) }
|
|
||||||
|
|
||||||
if filtered != newValue {
|
if let parsed = handle_string_amount(new_value: newValue) {
|
||||||
default_zap_amount = filtered
|
self.default_zap_amount = String(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
if filtered == "" {
|
|
||||||
set_default_zap_amount(pubkey: state.pubkey, amount: 1000)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ struct TextEvent: View {
|
|||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
let profile = damus.profiles.lookup(id: pubkey)
|
let profile = damus.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
|
let is_anon = event_is_anonymous(ev: event)
|
||||||
VStack {
|
VStack {
|
||||||
NavigationLink(destination: ProfileView(damus_state: damus, pubkey: pubkey)) {
|
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
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))")
|
Text(verbatim: "\(format_relative_time(event.created_at))")
|
||||||
.foregroundColor(.gray)
|
.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)
|
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")
|
||||||
|
}
|
||||||
|
|||||||
46
damus/Views/Profile/MaybeAnonPfpView.swift
Normal file
46
damus/Views/Profile/MaybeAnonPfpView.swift
Normal file
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
215
damus/Views/Zaps/CustomizeZapView.swift
Normal file
215
damus/Views/Zaps/CustomizeZapView.swift
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user