Merge branch 'master' into profile-markdown
21
damus/Assets.xcassets/bluewallet.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bluewallet.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/bluewallet.imageset/bluewallet.png
vendored
Normal file
|
After Width: | Height: | Size: 61 KiB |
21
damus/Assets.xcassets/cashapp.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "cashapp.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/cashapp.imageset/cashapp.png
vendored
Normal file
|
After Width: | Height: | Size: 44 KiB |
21
damus/Assets.xcassets/lnlink.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lnlink.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/lnlink.imageset/lnlink.png
vendored
Normal file
|
After Width: | Height: | Size: 547 KiB |
21
damus/Assets.xcassets/muun.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "muun.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/muun.imageset/muun.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
21
damus/Assets.xcassets/phoenix.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "phoenix.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/phoenix.imageset/phoenix.png
vendored
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
21
damus/Assets.xcassets/strike.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "strike.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/strike.imageset/strike.png
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
21
damus/Assets.xcassets/walletofsatoshi.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "walletofsatoshi.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/walletofsatoshi.imageset/walletofsatoshi.png
vendored
Normal file
|
After Width: | Height: | Size: 42 KiB |
21
damus/Assets.xcassets/zebedee.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zebedee.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/zebedee.imageset/zebedee.png
vendored
Normal file
|
After Width: | Height: | Size: 78 KiB |
21
damus/Assets.xcassets/zeusln.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zeus.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/zeusln.imageset/zeus.png
vendored
Normal file
|
After Width: | Height: | Size: 109 KiB |
@@ -122,6 +122,7 @@ struct ImageCarousel: View {
|
||||
TabView {
|
||||
ForEach(urls, id: \.absoluteString) { url in
|
||||
Rectangle()
|
||||
.foregroundColor(Color.clear)
|
||||
.overlay {
|
||||
KFAnimatedImage(url)
|
||||
.configure { view in
|
||||
@@ -136,6 +137,11 @@ struct ImageCarousel: View {
|
||||
Text(url.absoluteString)
|
||||
}
|
||||
.id(url.absoluteString)
|
||||
.contextMenu {
|
||||
Button("Copy Image") {
|
||||
UIPasteboard.general.string = url.absoluteString
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,38 @@
|
||||
import SwiftUI
|
||||
|
||||
struct InvoiceView: View {
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
let invoice: Invoice
|
||||
@State var showingSelectWallet: Bool = false
|
||||
@State var inv: String = ""
|
||||
|
||||
var PayButton: some View {
|
||||
Button("Pay") {
|
||||
guard let url = URL(string: "lightning:" + invoice.string) else {
|
||||
return
|
||||
}
|
||||
UIApplication.shared.open(url)
|
||||
Button {
|
||||
inv = invoice.string
|
||||
showingSelectWallet = true
|
||||
} label: {
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.foregroundColor(colorScheme == .light ? .black : .white)
|
||||
.overlay {
|
||||
Text("Pay")
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(colorScheme == .light ? .white : .black)
|
||||
}
|
||||
}
|
||||
//.buttonStyle(.bordered)
|
||||
.onTapGesture {
|
||||
// Temporary solution so that the "pay" button can be clicked (Yes we need an empty tap gesture)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.foregroundColor(.secondary.opacity(0.1))
|
||||
|
||||
VStack(alignment: .trailing, spacing: 12) {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
Label("", systemImage: "bolt.fill")
|
||||
.foregroundColor(.orange)
|
||||
@@ -36,9 +50,13 @@ struct InvoiceView: View {
|
||||
Text("\(invoice.amount / 1000) sats")
|
||||
.font(.title)
|
||||
PayButton
|
||||
.zIndex(5.0)
|
||||
.frame(height: 50)
|
||||
.zIndex(10.0)
|
||||
}
|
||||
.padding()
|
||||
.padding(30)
|
||||
}
|
||||
.sheet(isPresented: $showingSelectWallet, onDismiss: {showingSelectWallet = false}) {
|
||||
SelectWalletView(showingSelectWallet: $showingSelectWallet, invoice: $inv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ struct ContentView: View {
|
||||
self.active_sheet = .post
|
||||
}
|
||||
}
|
||||
}
|
||||
}.ignoresSafeArea(.keyboard, edges: .bottom)
|
||||
}
|
||||
.safeAreaInset(edge: .top) {
|
||||
VStack(spacing: 0) {
|
||||
@@ -228,6 +228,7 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline)
|
||||
.padding()
|
||||
}
|
||||
.onAppear() {
|
||||
self.connect()
|
||||
|
||||
@@ -15,6 +15,19 @@
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>muun</string>
|
||||
<string>zeusln</string>
|
||||
<string>zebedee</string>
|
||||
<string>lightning</string>
|
||||
<string>squarecash</string>
|
||||
<string>phoenix</string>
|
||||
<string>lnlink</string>
|
||||
<string>strike</string>
|
||||
<string>bluewallet</string>
|
||||
<string>walletofsatoshi</string>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
||||
@@ -190,6 +190,6 @@ class RelayPool {
|
||||
|
||||
func add_rw_relay(_ pool: RelayPool, _ url: String) {
|
||||
let url_ = URL(string: url)!
|
||||
try! pool.add_relay(url_, info: RelayInfo.rw)
|
||||
try? pool.add_relay(url_, info: RelayInfo.rw)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,19 @@ public class Constants {
|
||||
NostrEvent(id: UUID().description, content: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
|
||||
NostrEvent(id: UUID().description, content: "Bonjour Le Monde", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
|
||||
]
|
||||
|
||||
// New url prefixes needed to be added to LSApplicationQueriesSchemes
|
||||
static let WALLETS = """
|
||||
[
|
||||
{"id": 0, "name": "Strike", "link": "strike:", "appStoreLink": "https://apps.apple.com/us/app/strike-bitcoin-payments/id1488724463", "image": "strike"},
|
||||
{"id": 1, "name": "Cash App", "link": "squarecash://", "appStoreLink": "https://apps.apple.com/us/app/cash-app/id711923939", "image": "cashapp"},
|
||||
{"id": 2, "name": "Muun", "link": "muun:", "appStoreLink": "https://apps.apple.com/us/app/muun-wallet/id1482037683", "image": "muun"},
|
||||
{"id": 3, "name": "Blue Wallet", "link": "bluewallet:lightning:", "appStoreLink": "https://apps.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040", "image": "bluewallet"},
|
||||
{"id": 4, "name": "Wallet Of Satoshi", "link": "walletofsatoshi:lightning:", "appStoreLink": "https://apps.apple.com/us/app/wallet-of-satoshi/id1438599608", "image": "walletofsatoshi"},
|
||||
{"id": 5, "name": "Zebedee", "link": "zebedee:lightning:", "appStoreLink": "https://apps.apple.com/us/app/zebedee-wallet/id1484394401", "image": "zebedee"},
|
||||
{"id": 6, "name": "Zeus LN", "link": "zeusln:lightning:", "appStoreLink": "https://apps.apple.com/us/app/zeus-ln/id1456038895", "image": "zeusln"},
|
||||
{"id": 7, "name": "LNLink", "link": "lnlink:lightning:", "appStoreLink": "https://testflight.apple.com/join/aNY4yuuZ", "image": "lnlink"},
|
||||
{"id": 8, "name": "Phoenix", "link": "phoenix://", "appStoreLink": "https://apps.apple.com/us/app/phoenix-wallet/id1544097028", "image": "phoenix"},
|
||||
]
|
||||
""".data(using: .utf8)!
|
||||
}
|
||||
|
||||
39
damus/Util/LinkView.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// LinkView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Sam DuBois on 12/27/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import LinkPresentation
|
||||
|
||||
class CustomLinkView: LPLinkView {
|
||||
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
|
||||
}
|
||||
|
||||
struct LinkViewRepresentable: UIViewRepresentable {
|
||||
|
||||
typealias UIViewType = CustomLinkView
|
||||
|
||||
var metadata: LPLinkMetadata?
|
||||
var url: URL?
|
||||
|
||||
func makeUIView(context: Context) -> CustomLinkView {
|
||||
|
||||
if let metadata {
|
||||
let linkView = CustomLinkView(metadata: metadata)
|
||||
return linkView
|
||||
}
|
||||
|
||||
if let url {
|
||||
let linkView = CustomLinkView(url: url)
|
||||
return linkView
|
||||
}
|
||||
|
||||
return CustomLinkView()
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: CustomLinkView, context: Context) {
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,12 @@ func parse_digit(_ p: Parser) -> Int? {
|
||||
func parse_hex_char(_ p: Parser) -> Character? {
|
||||
let ind = p.str.index(p.str.startIndex, offsetBy: p.pos)
|
||||
|
||||
// Check that we're within the bounds of p.str's length
|
||||
if p.pos >= p.str.count {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
if let c = p.str[ind].unicodeScalars.first {
|
||||
// hex chars
|
||||
let d = c.value
|
||||
|
||||
@@ -17,9 +17,31 @@ struct AddRelayView: View {
|
||||
VStack(alignment: .leading) {
|
||||
Form {
|
||||
Section("Add Relay") {
|
||||
TextField("wss://some.relay.com", text: $relay)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
ZStack(alignment: .leading) {
|
||||
HStack{
|
||||
TextField("wss://some.relay.com", text: $relay)
|
||||
.padding(2)
|
||||
.padding(.leading, 25)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
|
||||
Label("", systemImage: "xmark.circle.fill")
|
||||
.foregroundColor(.blue)
|
||||
.padding(.trailing, -25.0)
|
||||
.opacity((relay == "") ? 0.0 : 1.0)
|
||||
.onTapGesture {
|
||||
self.relay = ""
|
||||
}
|
||||
}
|
||||
|
||||
Label("", systemImage: "doc.on.clipboard")
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
if let pastedrelay = UIPasteboard.general.string {
|
||||
self.relay = pastedrelay
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.content), size: .normal)
|
||||
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey), artifacts: .just_content(event.content), size: .normal)
|
||||
|
||||
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
||||
let bar = make_actionbar_model(ev: event, damus: damus_state)
|
||||
|
||||
@@ -102,12 +102,12 @@ struct ConfigView: View {
|
||||
.navigationTitle("Settings")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.alert("Logout", isPresented: $confirm_logout) {
|
||||
Button("Logout") {
|
||||
notify(.logout, ())
|
||||
}
|
||||
Button("Cancel") {
|
||||
confirm_logout = false
|
||||
}
|
||||
Button("Logout") {
|
||||
notify(.logout, ())
|
||||
}
|
||||
} message: {
|
||||
Text("Make sure your nsec account key is saved before you logout or you will lose access to this account")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ struct DMView: View {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_images(contacts: damus_state.contacts, ev: event), artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal)
|
||||
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||
|
||||
NoteContentView(privkey: damus_state.keypair.privkey, event: event, profiles: damus_state.profiles, show_images: should_show_img, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal)
|
||||
.foregroundColor(is_ours ? Color.white : Color.primary)
|
||||
.padding(10)
|
||||
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))
|
||||
|
||||
@@ -247,7 +247,9 @@ struct EventView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_images(contacts: damus.contacts, ev: event), artifacts: .just_content(content), size: self.size)
|
||||
let should_show_img = should_show_images(contacts: damus.contacts, ev: event, our_pubkey: damus.pubkey)
|
||||
|
||||
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, show_images: should_show_img, artifacts: .just_content(content), size: self.size)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.allowsHitTesting(!embedded)
|
||||
|
||||
@@ -309,7 +311,10 @@ struct EventView: View {
|
||||
}
|
||||
|
||||
// blame the porn bots for this code
|
||||
func should_show_images(contacts: Contacts, ev: NostrEvent) -> Bool {
|
||||
func should_show_images(contacts: Contacts, ev: NostrEvent, our_pubkey: String) -> Bool {
|
||||
if ev.pubkey == our_pubkey {
|
||||
return true
|
||||
}
|
||||
if contacts.is_in_friendosphere(ev.pubkey) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ struct FollowUserView: View {
|
||||
static let markdown = Markdown()
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
HStack {
|
||||
let pmodel = ProfileModel(pubkey: target.pubkey, damus: damus_state)
|
||||
let followers = FollowersModel(damus_state: damus_state, target: target.pubkey)
|
||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: followers)
|
||||
@@ -27,6 +27,8 @@ struct FollowUserView: View {
|
||||
ProfileName(pubkey: target.pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: false)
|
||||
if let about = profile?.about {
|
||||
Text(FollowUserView.markdown.process(about))
|
||||
.lineLimit(3)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +55,7 @@ struct FollowersView: View {
|
||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationBarTitle("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers")
|
||||
.onAppear {
|
||||
@@ -80,6 +83,7 @@ struct FollowingView: View {
|
||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.onAppear {
|
||||
following.subscribe()
|
||||
|
||||
@@ -76,14 +76,11 @@ struct TabBar: View {
|
||||
VStack {
|
||||
Divider()
|
||||
HStack {
|
||||
TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, action: action)
|
||||
TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, action: action)
|
||||
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, action: action)
|
||||
TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, action: action)
|
||||
TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("1")
|
||||
TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("2")
|
||||
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("3")
|
||||
TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("4")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import LinkPresentation
|
||||
|
||||
struct NoteArtifacts {
|
||||
let content: String
|
||||
let images: [URL]
|
||||
let invoices: [Invoice]
|
||||
let links: [URL]
|
||||
|
||||
static func just_content(_ content: String) -> NoteArtifacts {
|
||||
NoteArtifacts(content: content, images: [], invoices: [])
|
||||
NoteArtifacts(content: content, images: [], invoices: [], links: [])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +23,7 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
|
||||
let blocks = ev.blocks(privkey)
|
||||
var invoices: [Invoice] = []
|
||||
var img_urls: [URL] = []
|
||||
var link_urls: [URL] = []
|
||||
let txt = blocks.reduce("") { str, block in
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
@@ -33,14 +36,20 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
|
||||
invoices.append(invoice)
|
||||
return str
|
||||
case .url(let url):
|
||||
|
||||
// Handle Image URLs
|
||||
if is_image_url(url) {
|
||||
// Append Image
|
||||
img_urls.append(url)
|
||||
} else {
|
||||
link_urls.append(url)
|
||||
}
|
||||
return str + url.absoluteString
|
||||
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
return NoteArtifacts(content: txt, images: img_urls, invoices: invoices)
|
||||
return NoteArtifacts(content: txt, images: img_urls, invoices: invoices, links: link_urls)
|
||||
}
|
||||
|
||||
func is_image_url(_ url: URL) -> Bool {
|
||||
@@ -57,6 +66,7 @@ struct NoteContentView: View {
|
||||
|
||||
@State var artifacts: NoteArtifacts
|
||||
|
||||
@State var metaData: LPLinkMetadata? = nil
|
||||
let size: EventViewKind
|
||||
|
||||
func MainContent() -> some View {
|
||||
@@ -66,16 +76,33 @@ struct NoteContentView: View {
|
||||
|
||||
if show_images && artifacts.images.count > 0 {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
} else if !show_images && artifacts.images.count > 0 {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
.blur(radius: 10)
|
||||
.overlay {
|
||||
Rectangle()
|
||||
.opacity(0.50)
|
||||
}
|
||||
.cornerRadius(10)
|
||||
}
|
||||
if artifacts.invoices.count > 0 {
|
||||
InvoicesView(invoices: artifacts.invoices)
|
||||
.frame(width: 200)
|
||||
}
|
||||
|
||||
if show_images, self.metaData != nil {
|
||||
LinkViewRepresentable(metadata: self.metaData)
|
||||
} else {
|
||||
ForEach(artifacts.links, id:\.self) { link in
|
||||
LinkViewRepresentable(url: link)
|
||||
.frame(height: 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
MainContent()
|
||||
.animation(.easeInOut, value: metaData)
|
||||
.onAppear() {
|
||||
self.artifacts = render_note_content(ev: event, profiles: profiles, privkey: privkey)
|
||||
}
|
||||
@@ -95,6 +122,28 @@ struct NoteContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
if show_images, artifacts.links.count == 1 {
|
||||
|
||||
self.metaData = await getMetaData(for: artifacts.links.first!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getMetaData(for url: URL) async -> LPLinkMetadata? {
|
||||
// iOS 15 is crashing for some reason
|
||||
guard #available(iOS 16, *) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let provider = LPMetadataProvider()
|
||||
|
||||
do {
|
||||
return try await provider.startFetchingMetadata(for: url)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +169,7 @@ struct NoteContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let state = test_damus_state()
|
||||
let content = "hi there https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
|
||||
let artifacts = NoteArtifacts(content: content, images: [], invoices: [])
|
||||
let artifacts = NoteArtifacts(content: content, images: [], invoices: [], links: [])
|
||||
NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts, size: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ func PostButton(action: @escaping () -> ()) -> some View {
|
||||
radius: 3,
|
||||
x: 3,
|
||||
y: 3)
|
||||
.keyboardShortcut("n", modifiers: [.command, .shift])
|
||||
}
|
||||
|
||||
func PostButtonContainer(action: @escaping () -> ()) -> some View {
|
||||
|
||||
@@ -119,6 +119,8 @@ struct ProfileView: View {
|
||||
@StateObject var profile: ProfileModel
|
||||
@StateObject var followers: FollowersModel
|
||||
@State private var showingEditProfile = false
|
||||
@State var showingSelectWallet: Bool = false
|
||||
@State var inv: String = ""
|
||||
@State var is_zoomed: Bool = false
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@@ -126,9 +128,14 @@ struct ProfileView: View {
|
||||
|
||||
//@EnvironmentObject var profile: ProfileModel
|
||||
|
||||
func LNButton(_ url: URL, profile: Profile) -> some View {
|
||||
func LNButton(lud06: String?, lud16: String?, profile: Profile) -> some View {
|
||||
Button(action: {
|
||||
UIApplication.shared.open(url)
|
||||
if let l = lud06 {
|
||||
inv = l
|
||||
} else {
|
||||
inv = lud16 ?? ""
|
||||
}
|
||||
showingSelectWallet = true
|
||||
}) {
|
||||
Image(systemName: "bolt.circle")
|
||||
.symbolRenderingMode(.palette)
|
||||
@@ -138,9 +145,11 @@ struct ProfileView: View {
|
||||
Button {
|
||||
UIPasteboard.general.string = profile.lnurl ?? ""
|
||||
} label: {
|
||||
Label("Copy LNUrl", systemImage: "doc.on.doc")
|
||||
Label("Copy LNURL", systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
}.sheet(isPresented: $showingSelectWallet, onDismiss: {showingSelectWallet = false}) {
|
||||
SelectWalletView(showingSelectWallet: $showingSelectWallet, invoice: $inv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +181,10 @@ struct ProfileView: View {
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
if let profile = data {
|
||||
if let lnuri = profile.lightning_uri {
|
||||
LNButton(lnuri, profile: profile)
|
||||
if (profile.lud06 != nil || profile.lud16 != nil) {
|
||||
LNButton(lud06: profile.lud06, lud16: profile.lud16, profile: profile)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
92
damus/Views/SelectWalletView.swift
Normal file
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// SelectWalletView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Suhail Saqan on 12/22/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WalletItem : Decodable, Identifiable, Hashable {
|
||||
var id: Int
|
||||
var name : String
|
||||
var link : String
|
||||
var appStoreLink : String
|
||||
var image: String
|
||||
}
|
||||
|
||||
struct SelectWalletView: View {
|
||||
@Binding var showingSelectWallet: Bool
|
||||
@Binding var invoice: String
|
||||
@Environment(\.openURL) private var openURL
|
||||
@State var invoice_copied: Bool = false
|
||||
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
let walletItems = try! JSONDecoder().decode([WalletItem].self, from: Constants.WALLETS)
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section("Copy invoice") {
|
||||
HStack {
|
||||
Text(invoice).font(.body)
|
||||
.lineLimit(2)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: self.invoice_copied ? "checkmark.circle" : "doc.on.doc").foregroundColor(.blue)
|
||||
}.clipShape(RoundedRectangle(cornerRadius: 5)).onTapGesture {
|
||||
UIPasteboard.general.string = invoice
|
||||
self.invoice_copied = true
|
||||
generator.impactOccurred()
|
||||
}
|
||||
}
|
||||
Section("Select a lightning wallet"){
|
||||
List{
|
||||
Button() {
|
||||
if let url = URL(string: "lightning:\(invoice)"), UIApplication.shared.canOpenURL(url) {
|
||||
openURL(url)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Default Wallet").font(.body).foregroundColor(.blue)
|
||||
}
|
||||
}.buttonStyle(.plain)
|
||||
ForEach(walletItems, id: \.self) { wallet in
|
||||
Button() {
|
||||
if let url = URL(string: "\(wallet.link)\(invoice)"), UIApplication.shared.canOpenURL(url) {
|
||||
print("opening wallet url \(url)")
|
||||
openURL(url)
|
||||
} else {
|
||||
if let url = URL(string: wallet.appStoreLink), UIApplication.shared.canOpenURL(url) {
|
||||
openURL(url)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(wallet.image).resizable().frame(width: 32.0, height: 32.0,alignment: .center).cornerRadius(5)
|
||||
Text(wallet.name).font(.body)
|
||||
}
|
||||
}.buttonStyle(.plain)
|
||||
}
|
||||
}.padding(.vertical, 2.5)
|
||||
}
|
||||
}.navigationBarTitle(Text("Pay the lightning invoice"), displayMode: .inline).navigationBarItems(trailing: Button(action: {
|
||||
self.showingSelectWallet = false
|
||||
}) {
|
||||
Text("Done").bold()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SelectWalletView_Previews: PreviewProvider {
|
||||
@State static var show: Bool = true
|
||||
@State static var invoice: String = ""
|
||||
|
||||
static var previews: some View {
|
||||
SelectWalletView(showingSelectWallet: $show, invoice: $invoice)
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ struct ThreadV2View: View {
|
||||
.buttonStyle(.plain)
|
||||
.onAppear {
|
||||
// TODO: find another solution to prevent layout shifting and layout blocking on large responses
|
||||
reader.scrollTo("main", anchor: .center)
|
||||
reader.scrollTo("main", anchor: .bottom)
|
||||
}
|
||||
}
|
||||
}.background(GeometryReader { geometry in
|
||||
|
||||