Merge branch 'master' into profile-markdown

This commit is contained in:
Lio李歐
2022-12-29 09:57:54 -08:00
committed by GitHub
42 changed files with 568 additions and 48 deletions

View File

@@ -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 ## [0.1.8-5] - 2022-12-27
### Added ### Added

View File

@@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.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 */; }; 4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; };
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; }; 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; }; 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 */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; }; E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -152,6 +154,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
3165648A295B70D500C64604 /* LinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkView.swift; sourceTree = "<group>"; };
3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; }; 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; };
3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; 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 = "<group>"; }; 31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
@@ -309,6 +312,7 @@
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileName.swift; sourceTree = "<group>"; }; 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileName.swift; sourceTree = "<group>"; };
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; }; 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; }; 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; }; E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadV2View.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -480,6 +484,7 @@
4C216F33286F5ACD00040376 /* DMView.swift */, 4C216F33286F5ACD00040376 /* DMView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */, 4C06670028FC7C5900038D2A /* RelayView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */, E990020E2955F837003BBC5A /* EditMetadataView.swift */,
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */, E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
); );
path = Views; path = Views;
@@ -517,6 +522,7 @@
4C90BD19283AA67F008EE7EF /* Bech32.swift */, 4C90BD19283AA67F008EE7EF /* Bech32.swift */,
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */, 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */,
3169CAEC294FCCFC00EE4006 /* Constants.swift */, 3169CAEC294FCCFC00EE4006 /* Constants.swift */,
3165648A295B70D500C64604 /* LinkView.swift */,
); );
path = Util; path = Util;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -808,6 +814,7 @@
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */, 4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */, 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */, 4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */, 4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
@@ -838,6 +845,7 @@
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */, 4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */, 4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */,
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */, 4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */,
3165648B295B70D500C64604 /* LinkView.swift in Sources */,
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */, 4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */,
4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */, 4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */,
4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */, 4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */,
@@ -1031,7 +1039,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1070,7 +1078,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@@ -122,6 +122,7 @@ struct ImageCarousel: View {
TabView { TabView {
ForEach(urls, id: \.absoluteString) { url in ForEach(urls, id: \.absoluteString) { url in
Rectangle() Rectangle()
.foregroundColor(Color.clear)
.overlay { .overlay {
KFAnimatedImage(url) KFAnimatedImage(url)
.configure { view in .configure { view in
@@ -136,6 +137,11 @@ struct ImageCarousel: View {
Text(url.absoluteString) Text(url.absoluteString)
} }
.id(url.absoluteString) .id(url.absoluteString)
.contextMenu {
Button("Copy Image") {
UIPasteboard.general.string = url.absoluteString
}
}
} }
} }
} }

View File

@@ -8,24 +8,38 @@
import SwiftUI import SwiftUI
struct InvoiceView: View { struct InvoiceView: View {
@Environment(\.colorScheme) var colorScheme
let invoice: Invoice let invoice: Invoice
@State var showingSelectWallet: Bool = false
@State var inv: String = ""
var PayButton: some View { var PayButton: some View {
Button("Pay") { Button {
guard let url = URL(string: "lightning:" + invoice.string) else { inv = invoice.string
return showingSelectWallet = true
} } label: {
UIApplication.shared.open(url) 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 { var body: some View {
ZStack { ZStack {
RoundedRectangle(cornerRadius: 20) RoundedRectangle(cornerRadius: 10)
.foregroundColor(.secondary.opacity(0.1)) .foregroundColor(.secondary.opacity(0.1))
VStack(alignment: .trailing, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
HStack { HStack {
Label("", systemImage: "bolt.fill") Label("", systemImage: "bolt.fill")
.foregroundColor(.orange) .foregroundColor(.orange)
@@ -36,9 +50,13 @@ struct InvoiceView: View {
Text("\(invoice.amount / 1000) sats") Text("\(invoice.amount / 1000) sats")
.font(.title) .font(.title)
PayButton PayButton
.zIndex(5.0) .frame(height: 50)
.zIndex(10.0)
} }
.padding() .padding(30)
}
.sheet(isPresented: $showingSelectWallet, onDismiss: {showingSelectWallet = false}) {
SelectWalletView(showingSelectWallet: $showingSelectWallet, invoice: $inv)
} }
} }
} }

View File

@@ -88,7 +88,7 @@ struct ContentView: View {
self.active_sheet = .post self.active_sheet = .post
} }
} }
} }.ignoresSafeArea(.keyboard, edges: .bottom)
} }
.safeAreaInset(edge: .top) { .safeAreaInset(edge: .top) {
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -228,6 +228,7 @@ struct ContentView: View {
} }
TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline) TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline)
.padding()
} }
.onAppear() { .onAppear() {
self.connect() self.connect()

View File

@@ -15,6 +15,19 @@
</array> </array>
</dict> </dict>
</array> </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> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>

View File

@@ -190,6 +190,6 @@ class RelayPool {
func add_rw_relay(_ pool: RelayPool, _ url: String) { func add_rw_relay(_ pool: RelayPool, _ url: String) {
let url_ = URL(string: url)! let url_ = URL(string: url)!
try! pool.add_relay(url_, info: RelayInfo.rw) try? pool.add_relay(url_, info: RelayInfo.rw)
} }

View File

@@ -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: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Bonjour Le Monde", 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
View 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) {
}
}

View File

@@ -97,6 +97,12 @@ func parse_digit(_ p: Parser) -> Int? {
func parse_hex_char(_ p: Parser) -> Character? { func parse_hex_char(_ p: Parser) -> Character? {
let ind = p.str.index(p.str.startIndex, offsetBy: p.pos) 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 { if let c = p.str[ind].unicodeScalars.first {
// hex chars // hex chars
let d = c.value let d = c.value

View File

@@ -17,9 +17,31 @@ struct AddRelayView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Form { Form {
Section("Add Relay") { Section("Add Relay") {
TextField("wss://some.relay.com", text: $relay) ZStack(alignment: .leading) {
.autocorrectionDisabled(true) HStack{
.textInputAutocapitalization(.never) 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
}
}
}
} }
} }

View File

@@ -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 { if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
let bar = make_actionbar_model(ev: event, damus: damus_state) let bar = make_actionbar_model(ev: event, damus: damus_state)

View File

@@ -102,12 +102,12 @@ struct ConfigView: View {
.navigationTitle("Settings") .navigationTitle("Settings")
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
.alert("Logout", isPresented: $confirm_logout) { .alert("Logout", isPresented: $confirm_logout) {
Button("Logout") {
notify(.logout, ())
}
Button("Cancel") { Button("Cancel") {
confirm_logout = false confirm_logout = false
} }
Button("Logout") {
notify(.logout, ())
}
} message: { } message: {
Text("Make sure your nsec account key is saved before you logout or you will lose access to this account") Text("Make sure your nsec account key is saved before you logout or you will lose access to this account")
} }

View File

@@ -21,7 +21,9 @@ struct DMView: View {
Spacer() 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) .foregroundColor(is_ours ? Color.white : Color.primary)
.padding(10) .padding(10)
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15)) .background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))

View File

@@ -247,7 +247,9 @@ struct EventView: View {
.frame(maxWidth: .infinity, alignment: .leading) .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) .frame(maxWidth: .infinity, alignment: .leading)
.allowsHitTesting(!embedded) .allowsHitTesting(!embedded)
@@ -309,7 +311,10 @@ struct EventView: View {
} }
// blame the porn bots for this code // 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) { if contacts.is_in_friendosphere(ev.pubkey) {
return true return true
} }

View File

@@ -14,7 +14,7 @@ struct FollowUserView: View {
static let markdown = Markdown() static let markdown = Markdown()
var body: some View { var body: some View {
HStack(alignment: .top) { HStack {
let pmodel = ProfileModel(pubkey: target.pubkey, damus: damus_state) let pmodel = ProfileModel(pubkey: target.pubkey, damus: damus_state)
let followers = FollowersModel(damus_state: damus_state, target: target.pubkey) let followers = FollowersModel(damus_state: damus_state, target: target.pubkey)
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: followers) 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) ProfileName(pubkey: target.pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: false)
if let about = profile?.about { if let about = profile?.about {
Text(FollowUserView.markdown.process(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) FollowUserView(target: .pubkey(pk), damus_state: damus_state)
} }
} }
.padding()
} }
.navigationBarTitle("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers") .navigationBarTitle("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers")
.onAppear { .onAppear {
@@ -80,6 +83,7 @@ struct FollowingView: View {
FollowUserView(target: .pubkey(pk), damus_state: damus_state) FollowUserView(target: .pubkey(pk), damus_state: damus_state)
} }
} }
.padding()
} }
.onAppear { .onAppear {
following.subscribe() following.subscribe()

View File

@@ -76,14 +76,11 @@ struct TabBar: View {
VStack { VStack {
Divider() Divider()
HStack { HStack {
TabButton(timeline: .home, img: "house", 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) 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) 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) TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, action: action).keyboardShortcut("4")
} }
} }
} }
} }

View File

@@ -6,14 +6,16 @@
// //
import SwiftUI import SwiftUI
import LinkPresentation
struct NoteArtifacts { struct NoteArtifacts {
let content: String let content: String
let images: [URL] let images: [URL]
let invoices: [Invoice] let invoices: [Invoice]
let links: [URL]
static func just_content(_ content: String) -> NoteArtifacts { 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) let blocks = ev.blocks(privkey)
var invoices: [Invoice] = [] var invoices: [Invoice] = []
var img_urls: [URL] = [] var img_urls: [URL] = []
var link_urls: [URL] = []
let txt = blocks.reduce("") { str, block in let txt = blocks.reduce("") { str, block in
switch block { switch block {
case .mention(let m): case .mention(let m):
@@ -33,14 +36,20 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
invoices.append(invoice) invoices.append(invoice)
return str return str
case .url(let url): case .url(let url):
// Handle Image URLs
if is_image_url(url) { if is_image_url(url) {
// Append Image
img_urls.append(url) 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 { func is_image_url(_ url: URL) -> Bool {
@@ -57,6 +66,7 @@ struct NoteContentView: View {
@State var artifacts: NoteArtifacts @State var artifacts: NoteArtifacts
@State var metaData: LPLinkMetadata? = nil
let size: EventViewKind let size: EventViewKind
func MainContent() -> some View { func MainContent() -> some View {
@@ -66,16 +76,33 @@ struct NoteContentView: View {
if show_images && artifacts.images.count > 0 { if show_images && artifacts.images.count > 0 {
ImageCarousel(urls: artifacts.images) 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 { if artifacts.invoices.count > 0 {
InvoicesView(invoices: artifacts.invoices) 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 { var body: some View {
MainContent() MainContent()
.animation(.easeInOut, value: metaData)
.onAppear() { .onAppear() {
self.artifacts = render_note_content(ev: event, profiles: profiles, privkey: privkey) 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 { static var previews: some View {
let state = test_damus_state() let state = test_damus_state()
let content = "hi there https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg" 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) NoteContentView(privkey: "", event: NostrEvent(content: content, pubkey: "pk"), profiles: state.profiles, show_images: true, artifacts: artifacts, size: .normal)
} }
} }

View File

@@ -23,6 +23,7 @@ func PostButton(action: @escaping () -> ()) -> some View {
radius: 3, radius: 3,
x: 3, x: 3,
y: 3) y: 3)
.keyboardShortcut("n", modifiers: [.command, .shift])
} }
func PostButtonContainer(action: @escaping () -> ()) -> some View { func PostButtonContainer(action: @escaping () -> ()) -> some View {

View File

@@ -119,6 +119,8 @@ struct ProfileView: View {
@StateObject var profile: ProfileModel @StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel @StateObject var followers: FollowersModel
@State private var showingEditProfile = false @State private var showingEditProfile = false
@State var showingSelectWallet: Bool = false
@State var inv: String = ""
@State var is_zoomed: Bool = false @State var is_zoomed: Bool = false
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@@ -126,9 +128,14 @@ struct ProfileView: View {
//@EnvironmentObject var profile: ProfileModel //@EnvironmentObject var profile: ProfileModel
func LNButton(_ url: URL, profile: Profile) -> some View { func LNButton(lud06: String?, lud16: String?, profile: Profile) -> some View {
Button(action: { Button(action: {
UIApplication.shared.open(url) if let l = lud06 {
inv = l
} else {
inv = lud16 ?? ""
}
showingSelectWallet = true
}) { }) {
Image(systemName: "bolt.circle") Image(systemName: "bolt.circle")
.symbolRenderingMode(.palette) .symbolRenderingMode(.palette)
@@ -138,9 +145,11 @@ struct ProfileView: View {
Button { Button {
UIPasteboard.general.string = profile.lnurl ?? "" UIPasteboard.general.string = profile.lnurl ?? ""
} label: { } 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)
} }
} }
@@ -174,8 +183,8 @@ struct ProfileView: View {
Spacer() Spacer()
if let profile = data { if let profile = data {
if let lnuri = profile.lightning_uri { if (profile.lud06 != nil || profile.lud16 != nil) {
LNButton(lnuri, profile: profile) LNButton(lud06: profile.lud06, lud16: profile.lud16, profile: profile)
} }
} }

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

View File

@@ -245,7 +245,7 @@ struct ThreadV2View: View {
.buttonStyle(.plain) .buttonStyle(.plain)
.onAppear { .onAppear {
// TODO: find another solution to prevent layout shifting and layout blocking on large responses // 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 }.background(GeometryReader { geometry in

View File

@@ -53,6 +53,14 @@ class ReplyTests: XCTestCase {
XCTAssertEqual(blocks[0].is_text, content) 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() { func testHashtagsInQuote() {
let content = "This is my \"#awesome post\"" let content = "This is my \"#awesome post\""
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)

View File

@@ -24,7 +24,7 @@ sections = [
repo = 'damus-io/damus' 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"]) Link = namedtuple("Link", ["ref", "content", "url"])
@@ -46,14 +46,20 @@ def get_log_entries(commitrange):
commit = None commit = None
logs = git("log {commitrange}".format(commitrange=commitrange)) logs = git("log {commitrange}".format(commitrange=commitrange))
entries = [] entries = []
author = ""
for l in logs.split('\n'): for l in logs.split('\n'):
m = re.match(r'^commit ([A-Fa-f0-9]{40})$', l) m = re.match(r'^commit ([A-Fa-f0-9]{40})$', l)
a = re.match(r'^Author: ([^<]+)<.*$', l)
if m: if m:
commit = m.group(1) commit = m.group(1)
if a:
author = "(" + a.group(1)[:-1] + ")"
m = re.match( m = re.match(
r'^\s+Changelog-({}): (.*)$'.format("|".join(sections)), l, re.IGNORECASE) r'^\s+Changelog-({}): (.*)$'.format("|".join(sections)), l, re.IGNORECASE)
if not m: if not m:
continue continue
@@ -73,7 +79,7 @@ def get_log_entries(commitrange):
# pullreq = None # pullreq = None
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) entries.append(e)
return entries return entries
@@ -112,9 +118,9 @@ def commit_date(commitsha):
template = Template("""<%def name="group(entries)"> template = Template("""<%def name="group(entries)">
% for e in entries: % for e in entries:
% if e.pullreq is not None: % if e.pullreq is not None:
- ${e.content} ([#${e.pullreq}]) - ${e.content} ([#${e.pullreq}])
% else: % else:
- ${e.content} - ${e.content} ${e.author}
% endif % endif
% endfor % endfor