commit ac82f1bc09b1d421f3290fb3cf5c756ccbab7f03
parent 209f3e87594e873d61f5f0a1f268105f5508052c
Author: William Casarin <jb55@jb55.com>
Date: Thu, 20 Apr 2023 13:40:37 -0700
Add OnlyZaps Mode
Changelog-Added: Add OnlyZaps mode: disable reactions, only zaps!
Diffstat:
17 files changed, 335 insertions(+), 303 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -143,7 +143,6 @@
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; };
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */; };
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
- 4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
@@ -550,7 +549,6 @@
4C8D00D229E3C19F0036AF10 /* str_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = str_block.h; sourceTree = "<group>"; };
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP19Tests.swift; sourceTree = "<group>"; };
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; };
- 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
@@ -906,7 +904,6 @@
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */,
4C216F31286E388800040376 /* DMChatView.swift */,
4C216F33286F5ACD00040376 /* DMView.swift */,
- E990020E2955F837003BBC5A /* EditMetadataView.swift */,
3169CAE4294E699400EE4006 /* Empty Views */,
4C75EFB82804A2740006080F /* EventView.swift */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
@@ -961,7 +958,6 @@
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
4C363A8F28247A1D006E126D /* NostrLink.swift */,
- 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */,
);
path = Nostr;
sourceTree = "<group>";
@@ -1051,6 +1047,7 @@
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
+ E990020E2955F837003BBC5A /* EditMetadataView.swift */,
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
4C8682862814DE470026224F /* ProfileView.swift */,
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
@@ -1593,7 +1590,6 @@
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
- 4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */,
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -498,8 +498,25 @@ struct ContentView: View {
open_event(ev: target)
}
}
- .onReceive(handle_notify(.hide_reactions)) { notif in
+ .onReceive(handle_notify(.onlyzaps_mode)) { notif in
+ let hide = notif.object as! Bool
home.filter_events()
+
+ guard let damus_state else {
+ return
+ }
+
+ guard let profile = damus_state.profiles.lookup(id: damus_state.pubkey) else {
+ return
+ }
+
+ profile.reactions = !hide
+
+ guard let profile_ev = make_metadata_event(keypair: damus_state.keypair, metadata: profile) else {
+ return
+ }
+
+ damus_state.postbox.send(profile_ev)
}
.alert(NSLocalizedString("Deleted Account", comment: "Alert message to indicate this is a deleted account"), isPresented: $is_deleted_account) {
Button(NSLocalizedString("Logout", comment: "Button to close the alert that informs that the current account has been deleted.")) {
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -202,7 +202,7 @@ class HomeModel: ObservableObject {
}
notifications.filter { ev in
- if damus_state.settings.hide_reactions && ev.known_kind == NostrKind.like {
+ if damus_state.settings.onlyzaps_mode && ev.known_kind == NostrKind.like {
return false
}
@@ -261,7 +261,7 @@ class HomeModel: ObservableObject {
return
}
- if damus_state.settings.hide_reactions {
+ if damus_state.settings.onlyzaps_mode {
return
}
@@ -389,7 +389,7 @@ class HomeModel: ObservableObject {
NostrKind.text.rawValue,
NostrKind.boost.rawValue
]
- if !damus_state.settings.hide_reactions {
+ if !damus_state.settings.onlyzaps_mode {
home_filter_kinds.append(NostrKind.like.rawValue)
}
var home_filter = NostrFilter.filter_kinds(home_filter_kinds)
@@ -402,7 +402,7 @@ class HomeModel: ObservableObject {
NostrKind.boost.rawValue,
NostrKind.zap.rawValue,
]
- if !damus_state.settings.hide_reactions {
+ if !damus_state.settings.onlyzaps_mode {
notifications_filter_kinds.append(NostrKind.like.rawValue)
}
var notifications_filter = NostrFilter.filter_kinds(notifications_filter_kinds)
diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift
@@ -89,7 +89,7 @@ class ThreadModel: ObservableObject {
meta_events.referenced_ids = [event.id]
var kinds = [NostrKind.zap.rawValue, NostrKind.text.rawValue, NostrKind.boost.rawValue]
- if !damus_state.settings.hide_reactions {
+ if !damus_state.settings.onlyzaps_mode {
kinds.append(NostrKind.like.rawValue)
}
meta_events.kinds = kinds
diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift
@@ -202,9 +202,9 @@ class UserSettingsStore: ObservableObject {
}
}
- @Published var hide_reactions: Bool {
+ @Published var onlyzaps_mode: Bool {
didSet {
- UserDefaults.standard.set(hide_reactions, forKey: "hide_reactions")
+ UserDefaults.standard.set(onlyzaps_mode, forKey: "onlyzaps_mode")
}
}
@@ -302,7 +302,7 @@ class UserSettingsStore: ObservableObject {
disable_animation = should_disable_image_animation()
auto_translate = UserDefaults.standard.object(forKey: "auto_translate") as? Bool ?? true
show_only_preferred_languages = UserDefaults.standard.object(forKey: "show_only_preferred_languages") as? Bool ?? false
- hide_reactions = UserDefaults.standard.object(forKey: "hide_reactions") as? Bool ?? false
+ onlyzaps_mode = UserDefaults.standard.object(forKey: "hide_reactions") as? Bool ?? false
// Note from @tyiu:
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
diff --git a/damus/Nostr/Nostr.swift b/damus/Nostr/Nostr.swift
@@ -52,6 +52,11 @@ class Profile: Codable {
set_val(key, val)
}
+ var reactions: Bool? {
+ get { return get_val("reactions"); }
+ set(s) { set_val("reactions", s) }
+ }
+
var deleted: Bool? {
get { return get_val("deleted"); }
set(s) { set_val("deleted", s) }
diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift
@@ -560,7 +560,7 @@ func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
return ev
}
-func make_metadata_event(keypair: Keypair, metadata: NostrMetadata) -> NostrEvent? {
+func make_metadata_event(keypair: Keypair, metadata: Profile) -> NostrEvent? {
guard let privkey = keypair.privkey else {
return nil
}
diff --git a/damus/Nostr/NostrMetadata.swift b/damus/Nostr/NostrMetadata.swift
@@ -1,25 +0,0 @@
-//
-// NostrMetadata.swift
-// damus
-//
-// Created by William Casarin on 2022-05-21.
-//
-
-import Foundation
-
-
-struct NostrMetadata: Codable {
- let display_name: String?
- let name: String?
- let about: String?
- let website: String?
- let nip05: String?
- let picture: String?
- let banner: String?
- let lud06: String?
- let lud16: String?
-}
-
-func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
- return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: model.profile_image, banner: nil, lud06: nil, lud16: nil)
-}
diff --git a/damus/Util/Notifications.swift b/damus/Util/Notifications.swift
@@ -113,7 +113,7 @@ extension Notification.Name {
static var local_notification: Notification.Name {
return Notification.Name("local_notification")
}
- static var hide_reactions: Notification.Name {
+ static var onlyzaps_mode: Notification.Name {
return Notification.Name("hide_reactions")
}
}
diff --git a/damus/Views/ActionBar/EventActionBar.swift b/damus/Views/ActionBar/EventActionBar.swift
@@ -46,6 +46,18 @@ struct EventActionBar: View {
test_lnurl ?? damus_state.profiles.lookup(id: event.pubkey)?.lnurl
}
+ var show_like: Bool {
+ if settings.onlyzaps_mode {
+ return false
+ }
+
+ guard let profile = damus_state.profiles.lookup(id: event.pubkey) else {
+ return true
+ }
+
+ return profile.reactions ?? true
+ }
+
var body: some View {
HStack {
if damus_state.keypair.privkey != nil {
@@ -75,7 +87,7 @@ struct EventActionBar: View {
.foregroundColor(bar.boosted ? Color.green : Color.gray)
}
- if !settings.hide_reactions {
+ if show_like {
Spacer()
HStack(spacing: 4) {
diff --git a/damus/Views/ActionBar/EventDetailBar.swift b/damus/Views/ActionBar/EventDetailBar.swift
@@ -32,7 +32,7 @@ struct EventDetailBar: View {
.buttonStyle(PlainButtonStyle())
}
- if bar.likes > 0 && !state.settings.hide_reactions {
+ if bar.likes > 0 && !state.settings.onlyzaps_mode {
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
let noun = Text(verbatim: "\(reactionsCountString(bar.likes))").foregroundColor(.gray)
Text("\(Text("\(bar.likes)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
diff --git a/damus/Views/EditMetadataView.swift b/damus/Views/EditMetadataView.swift
@@ -1,246 +0,0 @@
-//
-// EditMetadataView.swift
-// damus
-//
-// Created by Thomas Tastet on 23/12/2022.
-//
-
-import SwiftUI
-import Combine
-
-let PPM_SIZE: CGFloat = 80.0
-let BANNER_HEIGHT: CGFloat = 150.0;
-
-func isHttpsUrl(_ string: String) -> Bool {
- let urlRegEx = "^https://.*$"
- let urlTest = NSPredicate(format:"SELF MATCHES %@", urlRegEx)
- return urlTest.evaluate(with: string)
-}
-
-func isImage(_ urlString: String) -> Bool {
- let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
-
- guard let url = URL(string: urlString) else {
- return false
- }
-
- var result = false
- let semaphore = DispatchSemaphore(value: 0)
-
- let task = URLSession.shared.dataTask(with: url) { data, response, error in
- if let error = error {
- print(error)
- semaphore.signal()
- return
- }
-
- guard let httpResponse = response as? HTTPURLResponse,
- let contentType = httpResponse.allHeaderFields["Content-Type"] as? String else {
- semaphore.signal()
- return
- }
-
- if imageTypes.contains(contentType.lowercased()) {
- result = true
- }
-
- semaphore.signal()
- }
-
- task.resume()
- semaphore.wait()
-
- return result
-}
-
-struct EditMetadataView: View {
- let damus_state: DamusState
- @State var display_name: String
- @State var about: String
- @State var picture: String
- @State var banner: String
- @State var nip05: String
- @State var name: String
- @State var ln: String
- @State var website: String
-
- @Environment(\.dismiss) var dismiss
- @Environment(\.colorScheme) var colorScheme
-
- @State var confirm_ln_address: Bool = false
- @StateObject var profileUploadViewModel = ProfileUploadingViewModel()
-
- init (damus_state: DamusState) {
- self.damus_state = damus_state
- let data = damus_state.profiles.lookup(id: damus_state.pubkey)
-
- _name = State(initialValue: data?.name ?? "")
- _display_name = State(initialValue: data?.display_name ?? "")
- _about = State(initialValue: data?.about ?? "")
- _website = State(initialValue: data?.website ?? "")
- _picture = State(initialValue: data?.picture ?? "")
- _banner = State(initialValue: data?.banner ?? "")
- _nip05 = State(initialValue: data?.nip05 ?? "")
- _ln = State(initialValue: data?.lud16 ?? data?.lud06 ?? "")
- }
-
- func imageBorderColor() -> Color {
- colorScheme == .light ? DamusColors.white : DamusColors.black
- }
-
- func save() {
- let metadata = NostrMetadata(
- display_name: display_name,
- name: name,
- about: about,
- website: website,
- nip05: nip05.isEmpty ? nil : nip05,
- picture: picture.isEmpty ? nil : picture,
- banner: banner.isEmpty ? nil : banner,
- lud06: ln.contains("@") ? nil : ln,
- lud16: ln.contains("@") ? ln : nil
- );
-
- let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
-
- if let metadata_ev = m_metadata_ev {
- damus_state.postbox.send(metadata_ev)
- }
- }
-
- func is_ln_valid(ln: String) -> Bool {
- return ln.contains("@") || ln.lowercased().starts(with: "lnurl")
- }
-
- var nip05_parts: NIP05? {
- return NIP05.parse(nip05)
- }
-
- var TopSection: some View {
- ZStack(alignment: .top) {
- GeometryReader { geo in
- BannerImageView(pubkey: damus_state.pubkey, profiles: damus_state.profiles)
- .aspectRatio(contentMode: .fill)
- .frame(width: geo.size.width, height: BANNER_HEIGHT)
- .clipped()
- }.frame(height: BANNER_HEIGHT)
- VStack(alignment: .leading) {
- let pfp_size: CGFloat = 90.0
-
- HStack(alignment: .center) {
- ProfilePictureSelector(pubkey: damus_state.pubkey, damus_state: damus_state, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
- .offset(y: -(pfp_size/2.0)) // Increase if set a frame
-
- Spacer()
- }.padding(.bottom,-(pfp_size/2.0))
- }
- .padding(.horizontal,18)
- .padding(.top,BANNER_HEIGHT)
- }
- }
-
- var body: some View {
- VStack(alignment: .leading) {
- TopSection
- Form {
- Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
- TextField("Satoshi Nakamoto", text: $display_name)
- .autocorrectionDisabled(true)
- .textInputAutocapitalization(.never)
- }
-
- Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
- TextField("satoshi", text: $name)
- .autocorrectionDisabled(true)
- .textInputAutocapitalization(.never)
-
- }
-
- Section (NSLocalizedString("Profile Picture", comment: "Label for Profile Picture section of user profile form.")) {
- TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $picture)
- .autocorrectionDisabled(true)
- .textInputAutocapitalization(.never)
- }
-
- Section (NSLocalizedString("Banner Image", comment: "Label for Banner Image section of user profile form.")) {
- TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $banner)
- .autocorrectionDisabled(true)
- .textInputAutocapitalization(.never)
- }
-
- Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
- TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
- .autocorrectionDisabled(true)
- .textInputAutocapitalization(.never)
- }
-
- Section(NSLocalizedString("About Me", comment: "Label for About Me section of user profile form.")) {
- let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.")
- ZStack(alignment: .topLeading) {
- TextEditor(text: $about)
- .textInputAutocapitalization(.sentences)
- .frame(minHeight: 20, alignment: .leading)
- .multilineTextAlignment(.leading)
- Text(about.isEmpty ? placeholder : about)
- .padding(.leading, 4)
- .opacity(about.isEmpty ? 1 : 0)
- .foregroundColor(Color(uiColor: .placeholderText))
- }
- }
-
- Section(NSLocalizedString("Bitcoin Lightning Tips", comment: "Label for Bitcoin Lightning Tips section of user profile form.")) {
- TextField(NSLocalizedString("Lightning Address or LNURL", comment: "Placeholder text for entry of Lightning Address or LNURL."), text: $ln)
- .autocorrectionDisabled(true)
- .textInputAutocapitalization(.never)
- }
-
- Section(content: {
- TextField(NSLocalizedString("jb55@jb55.com", comment: "Placeholder example text for identifier used for NIP-05 verification."), text: $nip05)
- .autocorrectionDisabled(true)
- .textInputAutocapitalization(.never)
- .onReceive(Just(nip05)) { newValue in
- self.nip05 = newValue.trimmingCharacters(in: .whitespaces)
- }
- }, header: {
- Text("NIP-05 Verification", comment: "Label for NIP-05 Verification section of user profile form.")
- }, footer: {
- if let parts = nip05_parts {
- Text("'\(parts.username)' at '\(parts.host)' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification.")
- } else if !nip05.isEmpty {
- Text("'\(nip05)' is an invalid NIP-05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid.")
- } else {
- Text("") // without this, the keyboard dismisses unnecessarily when the footer changes state
- }
- })
-
- Button(NSLocalizedString("Save", comment: "Button for saving profile.")) {
- if !ln.isEmpty && !is_ln_valid(ln: ln) {
- confirm_ln_address = true
- } else {
- save()
- dismiss()
- }
- }
- .disabled(profileUploadViewModel.isLoading)
- .alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) {
- Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) {
- }
- } message: {
- Text("The address should either begin with LNURL or should look like an email address.", comment: "Giving the description of the alert message.")
- }
- }
- }
- .ignoresSafeArea(edges: .top)
- .background(Color(.systemGroupedBackground))
- }
-
- func uploadedProfilePicture(image_url: URL?) {
- picture = image_url?.absoluteString ?? ""
- }
-}
-
-struct EditMetadataView_Previews: PreviewProvider {
- static var previews: some View {
- EditMetadataView(damus_state: test_damus_state())
- }
-}
diff --git a/damus/Views/Profile/EditMetadataView.swift b/damus/Views/Profile/EditMetadataView.swift
@@ -0,0 +1,252 @@
+//
+// EditMetadataView.swift
+// damus
+//
+// Created by Thomas Tastet on 23/12/2022.
+//
+
+import SwiftUI
+import Combine
+
+let PPM_SIZE: CGFloat = 80.0
+let BANNER_HEIGHT: CGFloat = 150.0;
+
+func isHttpsUrl(_ string: String) -> Bool {
+ let urlRegEx = "^https://.*$"
+ let urlTest = NSPredicate(format:"SELF MATCHES %@", urlRegEx)
+ return urlTest.evaluate(with: string)
+}
+
+func isImage(_ urlString: String) -> Bool {
+ let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
+
+ guard let url = URL(string: urlString) else {
+ return false
+ }
+
+ var result = false
+ let semaphore = DispatchSemaphore(value: 0)
+
+ let task = URLSession.shared.dataTask(with: url) { data, response, error in
+ if let error = error {
+ print(error)
+ semaphore.signal()
+ return
+ }
+
+ guard let httpResponse = response as? HTTPURLResponse,
+ let contentType = httpResponse.allHeaderFields["Content-Type"] as? String else {
+ semaphore.signal()
+ return
+ }
+
+ if imageTypes.contains(contentType.lowercased()) {
+ result = true
+ }
+
+ semaphore.signal()
+ }
+
+ task.resume()
+ semaphore.wait()
+
+ return result
+}
+
+struct EditMetadataView: View {
+ let damus_state: DamusState
+ @State var display_name: String
+ @State var about: String
+ @State var picture: String
+ @State var banner: String
+ @State var nip05: String
+ @State var name: String
+ @State var ln: String
+ @State var website: String
+ let profile: Profile?
+
+ @Environment(\.dismiss) var dismiss
+ @Environment(\.colorScheme) var colorScheme
+
+ @State var confirm_ln_address: Bool = false
+ @StateObject var profileUploadViewModel = ProfileUploadingViewModel()
+
+ init (damus_state: DamusState) {
+ self.damus_state = damus_state
+ let data = damus_state.profiles.lookup(id: damus_state.pubkey)
+ self.profile = data
+
+ _name = State(initialValue: data?.name ?? "")
+ _display_name = State(initialValue: data?.display_name ?? "")
+ _about = State(initialValue: data?.about ?? "")
+ _website = State(initialValue: data?.website ?? "")
+ _picture = State(initialValue: data?.picture ?? "")
+ _banner = State(initialValue: data?.banner ?? "")
+ _nip05 = State(initialValue: data?.nip05 ?? "")
+ _ln = State(initialValue: data?.lud16 ?? data?.lud06 ?? "")
+ }
+
+ func imageBorderColor() -> Color {
+ colorScheme == .light ? DamusColors.white : DamusColors.black
+ }
+
+ func to_profile() -> Profile {
+ let profile = self.profile ?? Profile()
+
+ profile.name = name
+ profile.display_name = display_name
+ profile.about = about
+ profile.website = website
+ profile.nip05 = nip05.isEmpty ? nil : nip05
+ profile.picture = picture.isEmpty ? nil : picture
+ profile.banner = banner.isEmpty ? nil : banner
+ profile.lud06 = ln.contains("@") ? nil : ln
+ profile.lud16 = ln.contains("@") ? ln : nil
+
+ return profile
+ }
+
+ func save() {
+ let profile = to_profile()
+ guard let metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: profile) else {
+ return
+ }
+ damus_state.postbox.send(metadata_ev)
+ }
+
+ func is_ln_valid(ln: String) -> Bool {
+ return ln.contains("@") || ln.lowercased().starts(with: "lnurl")
+ }
+
+ var nip05_parts: NIP05? {
+ return NIP05.parse(nip05)
+ }
+
+ var TopSection: some View {
+ ZStack(alignment: .top) {
+ GeometryReader { geo in
+ BannerImageView(pubkey: damus_state.pubkey, profiles: damus_state.profiles)
+ .aspectRatio(contentMode: .fill)
+ .frame(width: geo.size.width, height: BANNER_HEIGHT)
+ .clipped()
+ }.frame(height: BANNER_HEIGHT)
+ VStack(alignment: .leading) {
+ let pfp_size: CGFloat = 90.0
+
+ HStack(alignment: .center) {
+ ProfilePictureSelector(pubkey: damus_state.pubkey, damus_state: damus_state, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
+ .offset(y: -(pfp_size/2.0)) // Increase if set a frame
+
+ Spacer()
+ }.padding(.bottom,-(pfp_size/2.0))
+ }
+ .padding(.horizontal,18)
+ .padding(.top,BANNER_HEIGHT)
+ }
+ }
+
+ var body: some View {
+ VStack(alignment: .leading) {
+ TopSection
+ Form {
+ Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
+ TextField("Satoshi Nakamoto", text: $display_name)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ }
+
+ Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
+ TextField("satoshi", text: $name)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+
+ }
+
+ Section (NSLocalizedString("Profile Picture", comment: "Label for Profile Picture section of user profile form.")) {
+ TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $picture)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ }
+
+ Section (NSLocalizedString("Banner Image", comment: "Label for Banner Image section of user profile form.")) {
+ TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $banner)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ }
+
+ Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
+ TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ }
+
+ Section(NSLocalizedString("About Me", comment: "Label for About Me section of user profile form.")) {
+ let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.")
+ ZStack(alignment: .topLeading) {
+ TextEditor(text: $about)
+ .textInputAutocapitalization(.sentences)
+ .frame(minHeight: 20, alignment: .leading)
+ .multilineTextAlignment(.leading)
+ Text(about.isEmpty ? placeholder : about)
+ .padding(.leading, 4)
+ .opacity(about.isEmpty ? 1 : 0)
+ .foregroundColor(Color(uiColor: .placeholderText))
+ }
+ }
+
+ Section(NSLocalizedString("Bitcoin Lightning Tips", comment: "Label for Bitcoin Lightning Tips section of user profile form.")) {
+ TextField(NSLocalizedString("Lightning Address or LNURL", comment: "Placeholder text for entry of Lightning Address or LNURL."), text: $ln)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ }
+
+ Section(content: {
+ TextField(NSLocalizedString("jb55@jb55.com", comment: "Placeholder example text for identifier used for NIP-05 verification."), text: $nip05)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ .onReceive(Just(nip05)) { newValue in
+ self.nip05 = newValue.trimmingCharacters(in: .whitespaces)
+ }
+ }, header: {
+ Text("NIP-05 Verification", comment: "Label for NIP-05 Verification section of user profile form.")
+ }, footer: {
+ if let parts = nip05_parts {
+ Text("'\(parts.username)' at '\(parts.host)' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification.")
+ } else if !nip05.isEmpty {
+ Text("'\(nip05)' is an invalid NIP-05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid.")
+ } else {
+ Text("") // without this, the keyboard dismisses unnecessarily when the footer changes state
+ }
+ })
+
+ Button(NSLocalizedString("Save", comment: "Button for saving profile.")) {
+ if !ln.isEmpty && !is_ln_valid(ln: ln) {
+ confirm_ln_address = true
+ } else {
+ save()
+ dismiss()
+ }
+ }
+ .disabled(profileUploadViewModel.isLoading)
+ .alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) {
+ Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) {
+ }
+ } message: {
+ Text("The address should either begin with LNURL or should look like an email address.", comment: "Giving the description of the alert message.")
+ }
+ }
+ }
+ .ignoresSafeArea(edges: .top)
+ .background(Color(.systemGroupedBackground))
+ }
+
+ func uploadedProfilePicture(image_url: URL?) {
+ picture = image_url?.absoluteString ?? ""
+ }
+}
+
+struct EditMetadataView_Previews: PreviewProvider {
+ static var previews: some View {
+ EditMetadataView(damus_state: test_damus_state())
+ }
+}
diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift
@@ -245,20 +245,33 @@ struct ProfileView: View {
}
func lnButton(lnurl: String, profile: Profile) -> some View {
- Button(action: {
+ let button_img = profile.reactions == false ? "bolt.brakesignal" : "bolt.circle"
+ return Button(action: {
if damus_state.settings.show_wallet_selector {
showing_select_wallet = true
} else {
open_with_wallet(wallet: damus_state.settings.default_wallet.model, invoice: lnurl)
}
}) {
- Image(systemName: "bolt.circle")
+ Image(systemName: button_img)
.profile_button_style(scheme: colorScheme)
.contextMenu {
- Button {
- UIPasteboard.general.string = profile.lnurl ?? ""
- } label: {
- Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
+ if profile.reactions == false {
+ Text("OnlyZaps Enabled")
+ }
+
+ if let addr = profile.lud16 {
+ Button {
+ UIPasteboard.general.string = addr
+ } label: {
+ Label(addr, systemImage: "doc.on.doc")
+ }
+ } else if let lnurl = profile.lud06 {
+ Button {
+ UIPasteboard.general.string = profile.lnurl ?? ""
+ } label: {
+ Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
+ }
}
}
diff --git a/damus/Views/SaveKeysView.swift b/damus/Views/SaveKeysView.swift
@@ -217,3 +217,7 @@ struct SaveKeysView_Previews: PreviewProvider {
SaveKeysView(account: model)
}
}
+
+func create_account_to_metadata(_ model: CreateAccountModel) -> Profile {
+ return Profile(name: model.nick_name, display_name: model.real_name, about: model.about, picture: model.profile_image, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nil)
+}
diff --git a/damus/Views/Settings/AppearanceSettingsView.swift b/damus/Views/Settings/AppearanceSettingsView.swift
@@ -14,14 +14,6 @@ struct AppearanceSettingsView: View {
var body: some View {
Form {
- Section(header: Text(NSLocalizedString("Reactions", comment: "Section header for reaction settings"))) {
- Toggle(NSLocalizedString("Hide Reactions", comment: "Setting to hide reactions."), isOn: $settings.hide_reactions)
- .toggleStyle(.switch)
- .onChange(of: settings.hide_reactions) { newVal in
- notify(.hide_reactions, newVal)
- }
- }
-
Section(header: Text(NSLocalizedString("Text Truncation", comment: "Section header for damus text truncation user configuration"))) {
Toggle(NSLocalizedString("Truncate timeline text", comment: "Setting to truncate text in timeline"), isOn: $settings.truncate_timeline_text)
.toggleStyle(.switch)
diff --git a/damus/Views/Settings/ZapSettingsView.swift b/damus/Views/Settings/ZapSettingsView.swift
@@ -24,6 +24,18 @@ struct ZapSettingsView: View {
var body: some View {
Form {
+ Section(
+ header: Text(NSLocalizedString("OnlyZaps", comment: "Section header for enabling OnlyZaps mode (hide reactions)")),
+ footer: Text(NSLocalizedString("Hide all 🤙's. Others will not be able to send you 🤙's", comment: "Section footer describing onlyzaps mode"))
+
+ ) {
+ Toggle(NSLocalizedString("Enable OnlyZaps mode", comment: "Setting toggle to hide reactions."), isOn: $settings.onlyzaps_mode)
+ .toggleStyle(.switch)
+ .onChange(of: settings.onlyzaps_mode) { newVal in
+ notify(.onlyzaps_mode, newVal)
+ }
+ }
+
Section(NSLocalizedString("Wallet", comment: "Title for section in zap settings that controls the Lightning wallet selection.")) {
Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $settings.show_wallet_selector).toggleStyle(.switch)