Merge branch 'master' into add-wallet-modal
This commit is contained in:
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,30 @@
|
|||||||
|
## [0.1.8-4] - 2022-12-26
|
||||||
|
|
||||||
## [0.1.9] - 2022-12-23
|
### Added
|
||||||
|
|
||||||
|
- Long press lightning tip button to copy lnurl
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Only reload global view on pulldown refresh
|
||||||
|
- Save privkey in keychain instead of user defaults
|
||||||
|
- Also show inline images from friend-of-friends
|
||||||
|
- Show rounded corners on inline images
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix bug where typing the first character in the search box defocuses
|
||||||
|
- Fixed nip05 identifier format in profile editor
|
||||||
|
- Fix profile and event loading in global view
|
||||||
|
- Fix lightning tip button sometimes not working
|
||||||
|
- Make about me multi-line in profile editor
|
||||||
|
|
||||||
|
|
||||||
|
[0.1.8-4]: https://github.com/damus-io/damus/releases/tag/v0.1.8-4
|
||||||
|
|
||||||
|
## [0.1.8-3] - 2022-12-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -138,3 +163,5 @@
|
|||||||
|
|
||||||
|
|
||||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -69,13 +69,11 @@ damus implements the following [Nostr Implementation Possibilities][nips]
|
|||||||
- All your notifications except 💬 DMs
|
- All your notifications except 💬 DMs
|
||||||
|
|
||||||
#### 👤 Change Your Profile (PFP) and Bio
|
#### 👤 Change Your Profile (PFP) and Bio
|
||||||
- Currently you can't change your pfp on the Damus app (coming soon!). Here's how to do it on other clients (do at your own risk)
|
1. Go to your Profile Page on Damus app
|
||||||
1. Get the [Alby](https://getalby.com/) (Chrome, Brave, Firefox) or [nos2x](https://chrome.google.com/webstore/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp) browser extension (Chrome, Brave)
|
2. Tap on Edit button at the top
|
||||||
2. Go to https://damus.io/key to convert your nsec key (secret key in ⚙️ Settings) into a hex version
|
3. You will see text fields to update your information and bio
|
||||||
i. For Alby, right-click the extension, select Options and scroll to the Nostr section to enter your secret hex key
|
4. For PFP, insert a URL containing your image (support video: https://cdn.jb55.com/vid/pfp-editor.mp4)
|
||||||
ii. For nos2x, right-click the extension, select Options, then and add the relay `wss://relay.damus.io` and select both read and write, click Save, then enter your secret hex key and click save
|
5. Save
|
||||||
3. Visit https://metadata.nostr.com and your profile data should auto-populate from the extension. If not click the extension or refresh the page
|
|
||||||
4. Add your image using a hosting site like imgbb.com
|
|
||||||
|
|
||||||
#### ⚡️ Request Sats
|
#### ⚡️ Request Sats
|
||||||
(Sats or Satoshis are the smallest denomination of bitcoin)
|
(Sats or Satoshis are the smallest denomination of bitcoin)
|
||||||
|
|||||||
@@ -129,9 +129,9 @@
|
|||||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; };
|
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; };
|
||||||
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 */; };
|
||||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
|
||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||||
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
||||||
|
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -1031,7 +1031,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 = 3;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
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 +1070,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 = 3;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
@@ -41,12 +41,19 @@ class RelayPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func remove_handler(sub_id: String) {
|
func remove_handler(sub_id: String) {
|
||||||
handlers = handlers.filter { $0.sub_id != sub_id }
|
self.handlers = handlers.filter { $0.sub_id != sub_id }
|
||||||
|
print("removing \(sub_id) handler, current: \(handlers.count)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func register_handler(sub_id: String, handler: @escaping (String, NostrConnectionEvent) -> ()) {
|
func register_handler(sub_id: String, handler: @escaping (String, NostrConnectionEvent) -> ()) {
|
||||||
|
for handler in handlers {
|
||||||
|
// don't add duplicate handlers
|
||||||
|
if handler.sub_id == sub_id {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
self.handlers.append(RelayHandler(sub_id: sub_id, callback: handler))
|
self.handlers.append(RelayHandler(sub_id: sub_id, callback: handler))
|
||||||
|
print("registering \(sub_id) handler, current: \(self.handlers.count)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove_relay(_ relay_id: String) {
|
func remove_relay(_ relay_id: String) {
|
||||||
@@ -125,8 +132,10 @@ class RelayPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe(sub_id: String, to: [String]? = nil) {
|
func unsubscribe(sub_id: String, to: [String]? = nil) {
|
||||||
self.remove_handler(sub_id: sub_id)
|
if to == nil {
|
||||||
self.send(.unsubscribe(sub_id))
|
self.remove_handler(sub_id: sub_id)
|
||||||
|
}
|
||||||
|
self.send(.unsubscribe(sub_id), to: to)
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe(sub_id: String, filters: [NostrFilter], handler: @escaping (String, NostrConnectionEvent) -> ()) {
|
func subscribe(sub_id: String, filters: [NostrFilter], handler: @escaping (String, NostrConnectionEvent) -> ()) {
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ let PRIVKEY_HRP = "nsec"
|
|||||||
struct Keypair {
|
struct Keypair {
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
let privkey: String?
|
let privkey: String?
|
||||||
|
let pubkey_bech32: String
|
||||||
|
let privkey_bech32: String?
|
||||||
|
|
||||||
var pubkey_bech32: String {
|
init(pubkey: String, privkey: String?) {
|
||||||
return bech32_pubkey(pubkey)!
|
self.pubkey = pubkey
|
||||||
}
|
self.privkey = privkey
|
||||||
|
self.pubkey_bech32 = bech32_pubkey(pubkey) ?? pubkey
|
||||||
var privkey_bech32: String? {
|
self.privkey_bech32 = privkey.flatMap { bech32_privkey($0) }
|
||||||
return privkey.flatMap { bech32_privkey($0) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ func isHttpsUrl(_ string: String) -> Bool {
|
|||||||
return urlTest.evaluate(with: string)
|
return urlTest.evaluate(with: string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NIP05 {
|
||||||
|
let username: String
|
||||||
|
let host: String
|
||||||
|
}
|
||||||
|
|
||||||
func isImage(_ urlString: String) -> Bool {
|
func isImage(_ urlString: String) -> Bool {
|
||||||
let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
|
let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
|
||||||
|
|
||||||
@@ -95,6 +100,14 @@ struct EditMetadataView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nip05_parts: NIP05? {
|
||||||
|
let parts = nip05.split(separator: "@")
|
||||||
|
guard parts.count == 2 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return NIP05(username: String(parts[0]), host: String(parts[1]))
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -149,13 +162,17 @@ struct EditMetadataView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section(content: {
|
Section(content: {
|
||||||
TextField("example.com", text: $nip05)
|
TextField("jb55@jb55.com", text: $nip05)
|
||||||
.autocorrectionDisabled(true)
|
.autocorrectionDisabled(true)
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
}, header: {
|
}, header: {
|
||||||
Text("NIP-05 Verification")
|
Text("NIP-05 Verification")
|
||||||
}, footer: {
|
}, footer: {
|
||||||
Text("\(name)@\(nip05) will be used for verification")
|
if let parts = nip05_parts {
|
||||||
|
Text("'\(parts.username)' at '\(parts.host)' will be used for verification")
|
||||||
|
} else {
|
||||||
|
Text("'\(nip05)' is an invalid nip05 identifier. It should look like an email.")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Button("Save") {
|
Button("Save") {
|
||||||
|
|||||||
@@ -88,12 +88,12 @@ struct EventActionBar: View {
|
|||||||
}
|
}
|
||||||
.padding(.top, 1)
|
.padding(.top, 1)
|
||||||
.alert("Boost", isPresented: $confirm_boost) {
|
.alert("Boost", isPresented: $confirm_boost) {
|
||||||
Button("Boost") {
|
|
||||||
send_boost()
|
|
||||||
}
|
|
||||||
Button("Cancel") {
|
Button("Cancel") {
|
||||||
confirm_boost = false
|
confirm_boost = false
|
||||||
}
|
}
|
||||||
|
Button("Boost") {
|
||||||
|
send_boost()
|
||||||
|
}
|
||||||
} message: {
|
} message: {
|
||||||
Text("Are you sure you want to boost this post?")
|
Text("Are you sure you want to boost this post?")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ struct EventView: View {
|
|||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
let prof_model = ProfileModel(pubkey: event.pubkey, damus: damus)
|
let prof_model = ProfileModel(pubkey: event.pubkey, damus: damus)
|
||||||
let follow_model = FollowersModel(damus_state: damus, target: event.pubkey)
|
let follow_model = FollowersModel(damus_state: damus, target: event.pubkey)
|
||||||
|
let prof = damus.profiles.lookup(id: event.pubkey)
|
||||||
let booster_profile = ProfileView(damus_state: damus, profile: prof_model, followers: follow_model)
|
let booster_profile = ProfileView(damus_state: damus, profile: prof_model, followers: follow_model)
|
||||||
|
|
||||||
NavigationLink(destination: booster_profile) {
|
NavigationLink(destination: booster_profile) {
|
||||||
@@ -86,11 +87,9 @@ struct EventView: View {
|
|||||||
Image(systemName: "arrow.2.squarepath")
|
Image(systemName: "arrow.2.squarepath")
|
||||||
.font(.footnote.weight(.bold))
|
.font(.footnote.weight(.bold))
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
if let prof = damus.profiles.lookup(id: event.pubkey) {
|
ProfileName(pubkey: event.pubkey, profile: prof, contacts: damus.contacts, show_friend_confirmed: true)
|
||||||
Text(Profile.displayName(profile: prof, pubkey: event.pubkey))
|
|
||||||
.font(.footnote.weight(.bold))
|
.font(.footnote.weight(.bold))
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
}
|
|
||||||
Text("Boosted")
|
Text("Boosted")
|
||||||
.font(.footnote.weight(.bold))
|
.font(.footnote.weight(.bold))
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
@@ -214,7 +213,7 @@ extension View {
|
|||||||
Button {
|
Button {
|
||||||
UIPasteboard.general.string = event_to_json(ev: event)
|
UIPasteboard.general.string = event_to_json(ev: event)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Copy Note", systemImage: "note")
|
Label("Copy Note JSON", systemImage: "note")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ struct InnerProfilePicView: View {
|
|||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProfilePicView: View {
|
struct ProfilePicView: View {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ struct EditButton: View {
|
|||||||
|
|
||||||
struct ProfileView: View {
|
struct ProfileView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
let zoom_size: CGFloat = 350
|
||||||
|
|
||||||
@State private var selected_tab: ProfileTab = .posts
|
@State private var selected_tab: ProfileTab = .posts
|
||||||
@StateObject var profile: ProfileModel
|
@StateObject var profile: ProfileModel
|
||||||
@@ -120,6 +121,7 @@ struct ProfileView: View {
|
|||||||
@State private var showingEditProfile = false
|
@State private var showingEditProfile = false
|
||||||
@State var showingSelectWallet: Bool = false
|
@State var showingSelectWallet: Bool = false
|
||||||
@State var inv: String = ""
|
@State var inv: String = ""
|
||||||
|
@State var is_zoomed: Bool = false
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@@ -169,6 +171,12 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .custom(Color.black, 2), profiles: damus_state.profiles)
|
ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .custom(Color.black, 2), profiles: damus_state.profiles)
|
||||||
|
.onTapGesture {
|
||||||
|
is_zoomed.toggle()
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $is_zoomed) {
|
||||||
|
ProfilePicView(pubkey: profile.pubkey, size: zoom_size, highlight: .custom(Color.black, 2), profiles: damus_state.profiles)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ struct SearchHomeView: View {
|
|||||||
var SearchInput: some View {
|
var SearchInput: some View {
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
HStack{
|
HStack{
|
||||||
TextField("", text: $search)
|
TextField("Search...", text: $search)
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.padding(.leading, 35)
|
.padding(.leading, 35)
|
||||||
.autocorrectionDisabled(true)
|
.autocorrectionDisabled(true)
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
Label("", systemImage: "xmark.square")
|
Text("Cancel")
|
||||||
|
.foregroundColor(.blue)
|
||||||
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
|
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
|
||||||
.opacity((search == "") ? 0.0 : 1.0)
|
.opacity((search == "") ? 0.0 : 1.0)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
@@ -70,7 +71,9 @@ struct SearchHomeView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
MainContent
|
VStack {
|
||||||
|
MainContent
|
||||||
|
}
|
||||||
.safeAreaInset(edge: .top) {
|
.safeAreaInset(edge: .top) {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
SearchInput
|
SearchInput
|
||||||
@@ -85,8 +88,6 @@ struct SearchHomeView: View {
|
|||||||
print("search change 1")
|
print("search change 1")
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// TODO: This will always be empty when switching between tabs
|
|
||||||
// We'll need to store these in
|
|
||||||
if model.events.isEmpty {
|
if model.events.isEmpty {
|
||||||
model.subscribe()
|
model.subscribe()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user