diff --git a/CHANGELOG.md b/CHANGELOG.md index e71a2892..a1060ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## [0.1.8-6] - 2022-12-28 + +### Added + +- Lightning wallet selector (Suhail Saqan) +- Cmd-{1,2,3,4} to switch between tabs on MacOS (Jonathan Milligan) +- Shift-Cmd-N to create a post on MacOS (Jonathan Milligan) +- Link Previews! (Sam DuBois) +- Added paste and delete buttons to add relay field (Suhail Saqan) + + +### Changed + +- Blur and opaque non-friend images rather than only display the link (Sam DuBois) +- Remove URLs in content text when image is displayed (Sam DuBois) +- Show non-image URLs as clickable link views (Sam DuBois) +- Adjusted Pay button on invoices. (Sam DuBois) + + +### Fixed + +- Fix crash with @ sign in some posts (Pablo Fernandez) +- Swapped order of Logout and Cancel alert buttons (Terry Yiu) +- Fixed padding issue on tabbar on some devices (Sam DuBois) +- Fix post button moving after selecting from search result (OlegAba) +- Don't show white background on images in dark mode (William Casarin) + + + +[0.1.8-6]: https://github.com/damus-io/damus/releases/tag/v0.1.8-6 ## [0.1.8-5] - 2022-12-27 ### Added diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 3dfd52ce..0a549b9e 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3165648B295B70D500C64604 /* LinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3165648A295B70D500C64604 /* LinkView.swift */; }; 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; }; 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; }; @@ -130,6 +131,7 @@ 4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; }; 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; }; 6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; }; + BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; }; /* End PBXBuildFile section */ @@ -152,6 +154,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3165648A295B70D500C64604 /* LinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkView.swift; sourceTree = ""; }; 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = ""; }; 3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; 31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = ""; }; @@ -309,6 +312,7 @@ 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileName.swift; sourceTree = ""; }; 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = ""; }; 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = ""; }; + BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = ""; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = ""; }; E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -480,6 +484,7 @@ 4C216F33286F5ACD00040376 /* DMView.swift */, 4C06670028FC7C5900038D2A /* RelayView.swift */, E990020E2955F837003BBC5A /* EditMetadataView.swift */, + BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */, E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */, ); path = Views; @@ -517,6 +522,7 @@ 4C90BD19283AA67F008EE7EF /* Bech32.swift */, 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */, 3169CAEC294FCCFC00EE4006 /* Constants.swift */, + 3165648A295B70D500C64604 /* LinkView.swift */, ); path = Util; sourceTree = ""; @@ -808,6 +814,7 @@ 4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */, 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, + BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */, 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */, 4C3EA64928FF597700C48A62 /* bech32.c in Sources */, 4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */, @@ -838,6 +845,7 @@ 4C363AA428296DEE006E126D /* SearchModel.swift in Sources */, 4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */, 4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */, + 3165648B295B70D500C64604 /* LinkView.swift in Sources */, 4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */, 4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */, 4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */, @@ -1031,7 +1039,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_TEAM = XK7H4JAB3D; ENABLE_PREVIEWS = YES; @@ -1070,7 +1078,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_TEAM = XK7H4JAB3D; ENABLE_PREVIEWS = YES; diff --git a/damus/Assets.xcassets/bluewallet.imageset/Contents.json b/damus/Assets.xcassets/bluewallet.imageset/Contents.json new file mode 100644 index 00000000..8a564830 --- /dev/null +++ b/damus/Assets.xcassets/bluewallet.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/bluewallet.imageset/bluewallet.png b/damus/Assets.xcassets/bluewallet.imageset/bluewallet.png new file mode 100644 index 00000000..d6e32686 Binary files /dev/null and b/damus/Assets.xcassets/bluewallet.imageset/bluewallet.png differ diff --git a/damus/Assets.xcassets/cashapp.imageset/Contents.json b/damus/Assets.xcassets/cashapp.imageset/Contents.json new file mode 100644 index 00000000..bd570d56 --- /dev/null +++ b/damus/Assets.xcassets/cashapp.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/cashapp.imageset/cashapp.png b/damus/Assets.xcassets/cashapp.imageset/cashapp.png new file mode 100644 index 00000000..da042f7b Binary files /dev/null and b/damus/Assets.xcassets/cashapp.imageset/cashapp.png differ diff --git a/damus/Assets.xcassets/lnlink.imageset/Contents.json b/damus/Assets.xcassets/lnlink.imageset/Contents.json new file mode 100644 index 00000000..14eb0582 --- /dev/null +++ b/damus/Assets.xcassets/lnlink.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/lnlink.imageset/lnlink.png b/damus/Assets.xcassets/lnlink.imageset/lnlink.png new file mode 100644 index 00000000..03ba50b6 Binary files /dev/null and b/damus/Assets.xcassets/lnlink.imageset/lnlink.png differ diff --git a/damus/Assets.xcassets/muun.imageset/Contents.json b/damus/Assets.xcassets/muun.imageset/Contents.json new file mode 100644 index 00000000..e02c1bac --- /dev/null +++ b/damus/Assets.xcassets/muun.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/muun.imageset/muun.png b/damus/Assets.xcassets/muun.imageset/muun.png new file mode 100644 index 00000000..3c169619 Binary files /dev/null and b/damus/Assets.xcassets/muun.imageset/muun.png differ diff --git a/damus/Assets.xcassets/phoenix.imageset/Contents.json b/damus/Assets.xcassets/phoenix.imageset/Contents.json new file mode 100644 index 00000000..787bf0e6 --- /dev/null +++ b/damus/Assets.xcassets/phoenix.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/phoenix.imageset/phoenix.png b/damus/Assets.xcassets/phoenix.imageset/phoenix.png new file mode 100644 index 00000000..6b753f9a Binary files /dev/null and b/damus/Assets.xcassets/phoenix.imageset/phoenix.png differ diff --git a/damus/Assets.xcassets/strike.imageset/Contents.json b/damus/Assets.xcassets/strike.imageset/Contents.json new file mode 100644 index 00000000..3c05f9fc --- /dev/null +++ b/damus/Assets.xcassets/strike.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/strike.imageset/strike.png b/damus/Assets.xcassets/strike.imageset/strike.png new file mode 100644 index 00000000..4531d165 Binary files /dev/null and b/damus/Assets.xcassets/strike.imageset/strike.png differ diff --git a/damus/Assets.xcassets/walletofsatoshi.imageset/Contents.json b/damus/Assets.xcassets/walletofsatoshi.imageset/Contents.json new file mode 100644 index 00000000..1ddd11e4 --- /dev/null +++ b/damus/Assets.xcassets/walletofsatoshi.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/walletofsatoshi.imageset/walletofsatoshi.png b/damus/Assets.xcassets/walletofsatoshi.imageset/walletofsatoshi.png new file mode 100644 index 00000000..937c8f09 Binary files /dev/null and b/damus/Assets.xcassets/walletofsatoshi.imageset/walletofsatoshi.png differ diff --git a/damus/Assets.xcassets/zebedee.imageset/Contents.json b/damus/Assets.xcassets/zebedee.imageset/Contents.json new file mode 100644 index 00000000..0c4c1f0e --- /dev/null +++ b/damus/Assets.xcassets/zebedee.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/zebedee.imageset/zebedee.png b/damus/Assets.xcassets/zebedee.imageset/zebedee.png new file mode 100644 index 00000000..7903ae35 Binary files /dev/null and b/damus/Assets.xcassets/zebedee.imageset/zebedee.png differ diff --git a/damus/Assets.xcassets/zeusln.imageset/Contents.json b/damus/Assets.xcassets/zeusln.imageset/Contents.json new file mode 100644 index 00000000..301c442f --- /dev/null +++ b/damus/Assets.xcassets/zeusln.imageset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/zeusln.imageset/zeus.png b/damus/Assets.xcassets/zeusln.imageset/zeus.png new file mode 100644 index 00000000..a074cc98 Binary files /dev/null and b/damus/Assets.xcassets/zeusln.imageset/zeus.png differ diff --git a/damus/Components/ImageCarousel.swift b/damus/Components/ImageCarousel.swift index fb9c5af2..66a4ea24 100644 --- a/damus/Components/ImageCarousel.swift +++ b/damus/Components/ImageCarousel.swift @@ -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 + } + } } } } diff --git a/damus/Components/InvoiceView.swift b/damus/Components/InvoiceView.swift index 8ac1ab43..21bd0a73 100644 --- a/damus/Components/InvoiceView.swift +++ b/damus/Components/InvoiceView.swift @@ -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) } } } diff --git a/damus/ContentView.swift b/damus/ContentView.swift index cafec81d..a99006a2 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -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() diff --git a/damus/Info.plist b/damus/Info.plist index 3b4c1563..05e7955b 100644 --- a/damus/Info.plist +++ b/damus/Info.plist @@ -15,6 +15,19 @@ + LSApplicationQueriesSchemes + + muun + zeusln + zebedee + lightning + squarecash + phoenix + lnlink + strike + bluewallet + walletofsatoshi + NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift index aa797142..cdb204fa 100644 --- a/damus/Nostr/RelayPool.swift +++ b/damus/Nostr/RelayPool.swift @@ -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) } diff --git a/damus/Util/Constants.swift b/damus/Util/Constants.swift index 63199b11..07b5edd5 100644 --- a/damus/Util/Constants.swift +++ b/damus/Util/Constants.swift @@ -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)! } diff --git a/damus/Util/LinkView.swift b/damus/Util/LinkView.swift new file mode 100644 index 00000000..3d534b56 --- /dev/null +++ b/damus/Util/LinkView.swift @@ -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) { + } +} diff --git a/damus/Util/Parser.swift b/damus/Util/Parser.swift index 318824d4..0be32d6d 100644 --- a/damus/Util/Parser.swift +++ b/damus/Util/Parser.swift @@ -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 diff --git a/damus/Views/AddRelayView.swift b/damus/Views/AddRelayView.swift index b16447f3..17c1c5c1 100644 --- a/damus/Views/AddRelayView.swift +++ b/damus/Views/AddRelayView.swift @@ -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 + } + } + } } } diff --git a/damus/Views/ChatView.swift b/damus/Views/ChatView.swift index 3fc33ad2..36420848 100644 --- a/damus/Views/ChatView.swift +++ b/damus/Views/ChatView.swift @@ -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) diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift index eea1e059..88798102 100644 --- a/damus/Views/ConfigView.swift +++ b/damus/Views/ConfigView.swift @@ -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") } diff --git a/damus/Views/DMView.swift b/damus/Views/DMView.swift index be844de0..70852692 100644 --- a/damus/Views/DMView.swift +++ b/damus/Views/DMView.swift @@ -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)) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index 186e1590..8d42e28f 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -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 } diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift index f4656cc0..7e66f9c7 100644 --- a/damus/Views/FollowingView.swift +++ b/damus/Views/FollowingView.swift @@ -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() diff --git a/damus/Views/MainTabView.swift b/damus/Views/MainTabView.swift index 8bfeca26..e7666052 100644 --- a/damus/Views/MainTabView.swift +++ b/damus/Views/MainTabView.swift @@ -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") } } } } - - - diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift index ecb1c335..6705c560 100644 --- a/damus/Views/NoteContentView.swift +++ b/damus/Views/NoteContentView.swift @@ -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) } } diff --git a/damus/Views/PostButton.swift b/damus/Views/PostButton.swift index 866faa30..d1b82ddb 100644 --- a/damus/Views/PostButton.swift +++ b/damus/Views/PostButton.swift @@ -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 { diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift index 899bd9b4..14535ade 100644 --- a/damus/Views/ProfileView.swift +++ b/damus/Views/ProfileView.swift @@ -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) } } diff --git a/damus/Views/SelectWalletView.swift b/damus/Views/SelectWalletView.swift new file mode 100644 index 00000000..5573324f --- /dev/null +++ b/damus/Views/SelectWalletView.swift @@ -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) + } +} diff --git a/damus/Views/ThreadV2View.swift b/damus/Views/ThreadV2View.swift index c057a861..5738013e 100644 --- a/damus/Views/ThreadV2View.swift +++ b/damus/Views/ThreadV2View.swift @@ -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 diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift index cba280bb..f627fd67 100644 --- a/damusTests/ReplyTests.swift +++ b/damusTests/ReplyTests.swift @@ -53,6 +53,14 @@ class ReplyTests: XCTestCase { XCTAssertEqual(blocks[0].is_text, content) } + func testAtAtEnd() { + let content = "what @" + let blocks = parse_post_blocks(content: content) + + XCTAssertEqual(blocks.count, 1) + XCTAssertEqual(blocks[0].is_text, "what @") + } + func testHashtagsInQuote() { let content = "This is my \"#awesome post\"" let blocks = parse_post_blocks(content: content) diff --git a/devtools/changelog.py b/devtools/changelog.py index 9a055db3..5ac4340e 100755 --- a/devtools/changelog.py +++ b/devtools/changelog.py @@ -24,7 +24,7 @@ sections = [ repo = 'damus-io/damus' -Entry = namedtuple("Entry", ["commit", "pullreq", "content", "section"]) +Entry = namedtuple("Entry", ["commit", "pullreq", "content", "section", "author"]) Link = namedtuple("Link", ["ref", "content", "url"]) @@ -46,14 +46,20 @@ def get_log_entries(commitrange): commit = None logs = git("log {commitrange}".format(commitrange=commitrange)) entries = [] + author = "" for l in logs.split('\n'): m = re.match(r'^commit ([A-Fa-f0-9]{40})$', l) + a = re.match(r'^Author: ([^<]+)<.*$', l) if m: commit = m.group(1) + if a: + author = "(" + a.group(1)[:-1] + ")" + m = re.match( r'^\s+Changelog-({}): (.*)$'.format("|".join(sections)), l, re.IGNORECASE) + if not m: continue @@ -73,7 +79,7 @@ def get_log_entries(commitrange): # pullreq = None pullreq = None - e = Entry(commit, pullreq, m.group(2), m.group(1).lower()) + e = Entry(commit, pullreq, m.group(2), m.group(1).lower(), author) entries.append(e) return entries @@ -112,9 +118,9 @@ def commit_date(commitsha): template = Template("""<%def name="group(entries)"> % for e in entries: % if e.pullreq is not None: - - ${e.content} ([#${e.pullreq}]) +- ${e.content} ([#${e.pullreq}]) % else: - - ${e.content} +- ${e.content} ${e.author} % endif % endfor