Merge branch 'master' into exp-backoff
This commit is contained in:
@@ -26,14 +26,16 @@ struct EventDetailBar: View {
|
||||
HStack {
|
||||
if bar.boosts > 0 {
|
||||
NavigationLink(destination: RepostsView(damus_state: state, model: RepostsModel(state: state, target: target))) {
|
||||
Text("\(Text(verbatim: "\(bar.boosts)").font(.body.bold())) \(Text(String(format: Bundle.main.localizedString(forKey: "reposts_count", value: nil, table: nil), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
|
||||
let noun = Text(verbatim: "\(repostsCountString(bar.boosts))").foregroundColor(.gray)
|
||||
Text("\(Text("\(bar.boosts)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
if bar.likes > 0 {
|
||||
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
|
||||
Text("\(Text(verbatim: "\(bar.likes)").font(.body.bold())) \(Text(String(format: Bundle.main.localizedString(forKey: "reactions_count", value: nil, table: nil), bar.likes)).foregroundColor(.gray))", 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'.")
|
||||
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'.")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
@@ -41,7 +43,8 @@ struct EventDetailBar: View {
|
||||
if bar.zaps > 0 {
|
||||
let dst = ZapsView(state: state, target: .note(id: target, author: target_pk))
|
||||
NavigationLink(destination: dst) {
|
||||
Text("\(Text(verbatim: "\(bar.zaps)").font(.body.bold())) \(Text(String(format: Bundle.main.localizedString(forKey: "zaps_count", value: nil, table: nil), bar.zaps)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
|
||||
let noun = Text(verbatim: "\(zapsCountString(bar.zaps))").foregroundColor(.gray)
|
||||
Text("\(Text("\(bar.zaps)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
@@ -49,6 +52,21 @@ struct EventDetailBar: View {
|
||||
}
|
||||
}
|
||||
|
||||
func repostsCountString(_ count: Int, locale: Locale = Locale.current) -> String {
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
return String(format: bundle.localizedString(forKey: "reposts_count", value: nil, table: nil), locale: locale, count)
|
||||
}
|
||||
|
||||
func reactionsCountString(_ count: Int, locale: Locale = Locale.current) -> String {
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
return String(format: bundle.localizedString(forKey: "reactions_count", value: nil, table: nil), locale: locale, count)
|
||||
}
|
||||
|
||||
func zapsCountString(_ count: Int, locale: Locale = Locale.current) -> String {
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
return String(format: bundle.localizedString(forKey: "zaps_count", value: nil, table: nil), locale: locale, count)
|
||||
}
|
||||
|
||||
struct EventDetailBar_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EventDetailBar(state: test_damus_state(), target: "", target_pk: "")
|
||||
|
||||
@@ -129,26 +129,14 @@ struct ConfigView: View {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
|
||||
TextField(String("1000"), text: $default_zap_amount)
|
||||
.keyboardType(.numberPad)
|
||||
.onReceive(Just(default_zap_amount)) { newValue in
|
||||
let filtered = newValue.filter { Set("0123456789").contains($0) }
|
||||
|
||||
if filtered != newValue {
|
||||
default_zap_amount = filtered
|
||||
|
||||
if let parsed = handle_string_amount(new_value: newValue) {
|
||||
self.default_zap_amount = String(parsed)
|
||||
}
|
||||
|
||||
if filtered == "" {
|
||||
set_default_zap_amount(pubkey: state.pubkey, amount: 1000)
|
||||
return
|
||||
}
|
||||
|
||||
guard let amt = Int(filtered) else {
|
||||
return
|
||||
}
|
||||
set_default_zap_amount(pubkey: state.pubkey, amount: amt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,10 +208,10 @@ struct ConfigView: View {
|
||||
}
|
||||
}
|
||||
|
||||
let bundleShortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
|
||||
let bundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as! String
|
||||
Section(NSLocalizedString("Version", comment: "Section title for displaying the version number of the Damus app.")) {
|
||||
Text(verbatim: "\(bundleShortVersion) (\(bundleVersion))")
|
||||
if let bundleShortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"], let bundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] {
|
||||
Section(NSLocalizedString("Version", comment: "Section title for displaying the version number of the Damus app.")) {
|
||||
Text(verbatim: "\(bundleShortVersion) (\(bundleVersion))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,3 +334,18 @@ struct ConfigView_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func handle_string_amount(new_value: String) -> Int? {
|
||||
let filtered = new_value.filter { Set("0123456789").contains($0) }
|
||||
|
||||
if filtered == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let amt = Int(filtered) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return amt
|
||||
}
|
||||
|
||||
@@ -37,9 +37,7 @@ struct DMChatView: View {
|
||||
|
||||
var Header: some View {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
||||
let fmodel = FollowersModel(damus_state: damus_state, target: pubkey)
|
||||
let profile_page = ProfileView(damus_state: damus_state, profile: pmodel, followers: fmodel)
|
||||
let profile_page = ProfileView(damus_state: damus_state, pubkey: pubkey)
|
||||
return NavigationLink(destination: profile_page) {
|
||||
HStack {
|
||||
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
@@ -41,6 +41,9 @@ struct DirectMessagesView: View {
|
||||
ForEach(dms, id: \.0) { tup in
|
||||
MaybeEvent(tup)
|
||||
.padding(.top, 10)
|
||||
|
||||
Divider()
|
||||
.padding([.top], 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func scroll_after_load(thread: ThreadModel, proxy: ScrollViewProxy) {
|
||||
|
||||
struct EventDetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let state = test_damus_state()
|
||||
let _ = test_damus_state()
|
||||
EventDetailView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,8 @@ struct EventView: View {
|
||||
if event.known_kind == .boost {
|
||||
if let inner_ev = event.inner_event {
|
||||
VStack(alignment: .leading) {
|
||||
let prof_model = ProfileModel(pubkey: event.pubkey, damus: damus)
|
||||
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, pubkey: event.pubkey)
|
||||
|
||||
NavigationLink(destination: booster_profile) {
|
||||
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
|
||||
@@ -86,9 +84,6 @@ struct EventView: View {
|
||||
TextEvent(damus: damus, event: event, pubkey: pubkey, has_action_bar: has_action_bar, booster_pubkey: nil)
|
||||
.padding([.top], 6)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding([.top], 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,10 @@ struct BuilderEventView: View {
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.cornerRadius(8)
|
||||
.border(Color.gray.opacity(0.2), width: 1)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.gray.opacity(0.2), lineWidth: 1.0)
|
||||
)
|
||||
.onAppear {
|
||||
self.load()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,14 @@ struct EventBody: View {
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
let size: EventViewKind
|
||||
let should_show_img: Bool
|
||||
|
||||
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_show_img: Bool? = nil) {
|
||||
self.damus_state = damus_state
|
||||
self.event = event
|
||||
self.size = size
|
||||
self.should_show_img = should_show_img ?? should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||
}
|
||||
|
||||
var content: String {
|
||||
event.get_content(damus_state.keypair.privkey)
|
||||
@@ -21,8 +29,6 @@ struct EventBody: View {
|
||||
ReplyDescription(event: event, profiles: damus_state.profiles)
|
||||
}
|
||||
|
||||
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey, booster_pubkey: nil)
|
||||
|
||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ struct EventMenuContext: View {
|
||||
notify(.update_bookmarks, event)
|
||||
} label: {
|
||||
let imageName = isBookmarked ? "bookmark.fill" : "bookmark"
|
||||
let unBookmarkString = NSLocalizedString("Un-Bookmark", comment: "Context menu option for un-bookmarking a note")
|
||||
let bookmarkString = NSLocalizedString("Bookmark", comment: "Context menu optoin for bookmarking a note")
|
||||
Label(isBookmarked ? unBookmarkString : bookmarkString, systemImage: imageName)
|
||||
let removeBookmarkString = NSLocalizedString("Remove Bookmark", comment: "Context menu option for removing a note bookmark.")
|
||||
let addBookmarkString = NSLocalizedString("Add Bookmark", comment: "Context menu option for adding a note bookmark.")
|
||||
Label(isBookmarked ? removeBookmarkString : addBookmarkString, systemImage: imageName)
|
||||
}
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
|
||||
@@ -31,10 +31,7 @@ struct EventProfile: View {
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
VStack {
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: FollowersModel(damus_state: damus_state, target: pubkey))
|
||||
|
||||
NavigationLink(destination: pv) {
|
||||
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
|
||||
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,15 @@ struct ReplyDescription_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
||||
func reply_desc(profiles: Profiles, event: NostrEvent, locale: Locale = Locale.current) -> String {
|
||||
let desc = make_reply_description(event.tags)
|
||||
let pubkeys = desc.pubkeys
|
||||
let n = desc.others
|
||||
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
|
||||
if desc.pubkeys.count == 0 {
|
||||
return NSLocalizedString("Replying to self", comment: "Label to indicate that the user is replying to themself.")
|
||||
return NSLocalizedString("Replying to self", bundle: bundle, comment: "Label to indicate that the user is replying to themself.")
|
||||
}
|
||||
|
||||
let names: [String] = pubkeys.map {
|
||||
@@ -40,20 +42,16 @@ func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
||||
return Profile.displayName(profile: prof, pubkey: $0)
|
||||
}
|
||||
|
||||
let othersCount = n - pubkeys.count
|
||||
if names.count > 1 {
|
||||
let othersCount = n - pubkeys.count
|
||||
if othersCount == 0 {
|
||||
return String(format: "Replying to %@ & %@", names[0], names[1])
|
||||
return String(format: NSLocalizedString("Replying to %@ & %@", bundle: bundle, comment: "Label to indicate that the user is replying to 2 users."), locale: locale, names[0], names[1])
|
||||
} else {
|
||||
return String(format: "Replying to %@, %@ & %d others", names[0], names[1], othersCount)
|
||||
return String(format: bundle.localizedString(forKey: "replying_to_two_and_others", value: nil, table: nil), locale: locale, othersCount, names[0], names[1])
|
||||
}
|
||||
}
|
||||
|
||||
if othersCount == 0 {
|
||||
return String(format: "Replying to %@", names[0])
|
||||
} else {
|
||||
return String(format: "Replying to %@ & %d others", names[0], othersCount)
|
||||
}
|
||||
return String(format: NSLocalizedString("Replying to %@", bundle: bundle, comment: "Label to indicate that the user is replying to 1 user."), locale: locale, names[0])
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,20 +18,17 @@ struct TextEvent: View {
|
||||
HStack(alignment: .top) {
|
||||
let profile = damus.profiles.lookup(id: pubkey)
|
||||
|
||||
let is_anon = event_is_anonymous(ev: event)
|
||||
VStack {
|
||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
||||
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
||||
|
||||
NavigationLink(destination: pv) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
|
||||
}
|
||||
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
EventProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
|
||||
let pk = is_anon ? "anon" : pubkey
|
||||
EventProfileName(pubkey: pk, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
|
||||
|
||||
Text(verbatim: "\(format_relative_time(event.created_at))")
|
||||
.foregroundColor(.gray)
|
||||
@@ -68,3 +65,18 @@ struct TextEvent_Previews: PreviewProvider {
|
||||
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", has_action_bar: true, booster_pubkey: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
|
||||
for t in ev.tags {
|
||||
if t.count >= 1 && t[0] == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func event_is_anonymous(ev: NostrEvent) -> Bool {
|
||||
return ev.known_kind == .zap_request && event_has_tag(ev: ev, tag: "anon")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ struct FollowButtonView: View {
|
||||
Button {
|
||||
follow_state = perform_follow_btn_action(follow_state, target: target)
|
||||
} label: {
|
||||
Text(follow_btn_txt(follow_state, follows_you: follows_you))
|
||||
Text(verbatim: "\(follow_btn_txt(follow_state, follows_you: follows_you))")
|
||||
.frame(width: 105, height: 30)
|
||||
//.padding(.vertical, 10)
|
||||
.font(.caption.weight(.bold))
|
||||
|
||||
@@ -29,7 +29,6 @@ struct FollowersView: View {
|
||||
@EnvironmentObject var followers: FollowersModel
|
||||
|
||||
var body: some View {
|
||||
let profile = damus_state.profiles.lookup(id: whos)
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
ForEach(followers.contacts ?? [], id: \.self) { pk in
|
||||
@@ -38,7 +37,7 @@ struct FollowersView: View {
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationBarTitle(NSLocalizedString("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers", comment: "Navigation bar title for view that shows who is following a user."))
|
||||
.navigationBarTitle(NSLocalizedString("Followers", comment: "Navigation bar title for view that shows who is following a user."))
|
||||
.onAppear {
|
||||
followers.subscribe()
|
||||
}
|
||||
@@ -56,8 +55,6 @@ struct FollowingView: View {
|
||||
let whos: String
|
||||
|
||||
var body: some View {
|
||||
let profile = damus_state.profiles.lookup(id: whos)
|
||||
let who = Profile.displayName(profile: profile, pubkey: whos)
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
ForEach(following.contacts, id: \.self) { pk in
|
||||
@@ -72,7 +69,7 @@ struct FollowingView: View {
|
||||
.onDisappear {
|
||||
following.unsubscribe()
|
||||
}
|
||||
.navigationBarTitle(NSLocalizedString("\(who) following", comment: "Navigation bar title for view that shows who a user is following."))
|
||||
.navigationBarTitle(NSLocalizedString("Following", comment: "Navigation bar title for view that shows who a user is following."))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
214
damus/Views/Notifications/EventGroupView.swift
Normal file
214
damus/Views/Notifications/EventGroupView.swift
Normal file
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// RepostGroupView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
enum EventGroupType {
|
||||
case repost(EventGroup)
|
||||
case reaction(EventGroup)
|
||||
case zap(ZapGroup)
|
||||
case profile_zap(ZapGroup)
|
||||
|
||||
var events: [NostrEvent] {
|
||||
switch self {
|
||||
case .repost(let grp):
|
||||
return grp.events
|
||||
case .reaction(let grp):
|
||||
return grp.events
|
||||
case .zap(let zapgrp):
|
||||
return zapgrp.zap_requests()
|
||||
case .profile_zap(let zapgrp):
|
||||
return zapgrp.zap_requests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ReactingTo {
|
||||
case your_post
|
||||
case tagged_in
|
||||
case your_profile
|
||||
}
|
||||
|
||||
func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
|
||||
guard let ev else {
|
||||
return .your_profile
|
||||
}
|
||||
|
||||
if ev.pubkey == our_pubkey {
|
||||
return .your_post
|
||||
}
|
||||
|
||||
return .tagged_in
|
||||
}
|
||||
|
||||
func event_author_name(profiles: Profiles, _ ev: NostrEvent) -> String {
|
||||
let alice_pk = ev.pubkey
|
||||
let alice_prof = profiles.lookup(id: alice_pk)
|
||||
return Profile.displayName(profile: alice_prof, pubkey: alice_pk)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a notification string describing user actions in response to an event group type.
|
||||
|
||||
The localization keys read by this function are the following (although some keys may not actually be used in practice):
|
||||
|
||||
"??" - returned when there are no events associated with the specified event group type.
|
||||
|
||||
"reacted_tagged_in_1" - returned when 1 reaction occurred to a post that the current user was tagged in
|
||||
"reacted_tagged_in_2" - returned when 2 reactions occurred to a post that the current user was tagged in
|
||||
"reacted_tagged_in_3" - returned when 3 or more reactions occurred to a post that the current user was tagged in
|
||||
"reacted_your_post_1" - returned when 1 reaction occurred to the current user's post
|
||||
"reacted_your_post_2" - returned when 2 reactions occurred to the current user's post
|
||||
"reacted_your_post_3" - returned when 3 or more reactions occurred to the current user's post
|
||||
"reacted_your_profile_1" - returned when 1 reaction occurred to the current user's profile
|
||||
"reacted_your_profile_2" - returned when 2 reactions occurred to the current user's profile
|
||||
"reacted_your_profile_3" - returned when 3 or more reactions occurred to the current user's profile
|
||||
|
||||
"reposted_tagged_in_1" - returned when 1 repost occurred to a post that the current user was tagged in
|
||||
"reposted_tagged_in_2" - returned when 2 reposts occurred to a post that the current user was tagged in
|
||||
"reposted_tagged_in_3" - returned when 3 or more reposts occurred to a post that the current user was tagged in
|
||||
"reposted_your_post_1" - returned when 1 repost occurred to the current user's post
|
||||
"reposted_your_post_2" - returned when 2 reposts occurred to the current user's post
|
||||
"reposted_your_post_3" - returned when 3 or more reposts occurred to the current user's post
|
||||
"reposted_your_profile_1" - returned when 1 repost occurred to the current user's profile
|
||||
"reposted_your_profile_2" - returned when 2 reposts occurred to the current user's profile
|
||||
"reposted_your_profile_3" - returned when 3 or more reposts occurred to the current user's profile
|
||||
|
||||
"zapped_tagged_in_1" - returned when 1 zap occurred to a post that the current user was tagged in
|
||||
"zapped_tagged_in_2" - returned when 2 zaps occurred to a post that the current user was tagged in
|
||||
"zapped_tagged_in_3" - returned when 3 or more zaps occurred to a post that the current user was tagged in
|
||||
"zapped_your_post_1" - returned when 1 zap occurred to the current user's post
|
||||
"zapped_your_post_2" - returned when 2 zaps occurred to the current user's post
|
||||
"zapped_your_post_3" - returned when 3 or more zaps occurred to the current user's post
|
||||
"zapped_your_profile_1" - returned when 1 zap occurred to the current user's profile
|
||||
"zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile
|
||||
"zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile
|
||||
*/
|
||||
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, locale: Locale? = nil) -> String {
|
||||
let verb = reacting_to_verb(group: group)
|
||||
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
|
||||
let localization_key = "\(verb)_\(reacting_to)_\(min(group.events.count, 3))"
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
|
||||
switch group.events.count {
|
||||
case 0:
|
||||
return NSLocalizedString("??", comment: "")
|
||||
case 1:
|
||||
let ev = group.events.first!
|
||||
let profile = profiles.lookup(id: ev.pubkey)
|
||||
let display_name = Profile.displayName(profile: profile, pubkey: ev.pubkey)
|
||||
|
||||
return String(format: bundle.localizedString(forKey: localization_key, value: bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: localization_key, value: nil, table: nil), table: nil), locale: locale, display_name)
|
||||
case 2:
|
||||
let alice_name = event_author_name(profiles: profiles, group.events[0])
|
||||
let bob_name = event_author_name(profiles: profiles, group.events[1])
|
||||
|
||||
return String(format: bundle.localizedString(forKey: localization_key, value: bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: localization_key, value: nil, table: nil), table: nil), locale: locale, alice_name, bob_name)
|
||||
default:
|
||||
let alice_name = event_author_name(profiles: profiles, group.events.first!)
|
||||
let count = group.events.count - 1
|
||||
|
||||
return String(format: bundle.localizedString(forKey: localization_key, value: bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: localization_key, value: nil, table: nil), table: nil), locale: locale, count, alice_name)
|
||||
}
|
||||
}
|
||||
|
||||
func reacting_to_verb(group: EventGroupType) -> String {
|
||||
switch group {
|
||||
case .reaction:
|
||||
return "reacted"
|
||||
case .repost:
|
||||
return "reposted"
|
||||
case .zap: fallthrough
|
||||
case .profile_zap:
|
||||
return "zapped"
|
||||
}
|
||||
}
|
||||
|
||||
struct EventGroupView: View {
|
||||
let state: DamusState
|
||||
let event: NostrEvent?
|
||||
let group: EventGroupType
|
||||
|
||||
var GroupDescription: some View {
|
||||
Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event))")
|
||||
}
|
||||
|
||||
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
|
||||
let fmt = format_msats_abbrev(zapgrp.msat_total)
|
||||
return VStack(alignment: .center) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.foregroundColor(.orange)
|
||||
Text("\(fmt)")
|
||||
.foregroundColor(Color.orange)
|
||||
}
|
||||
}
|
||||
|
||||
var GroupIcon: some View {
|
||||
Group {
|
||||
switch group {
|
||||
case .repost:
|
||||
Image(systemName: "arrow.2.squarepath")
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
case .reaction:
|
||||
Image("shaka-full")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(.accentColor)
|
||||
case .profile_zap(let zapgrp):
|
||||
ZapIcon(zapgrp)
|
||||
case .zap(let zapgrp):
|
||||
ZapIcon(zapgrp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
GroupIcon
|
||||
.frame(width: PFP_SIZE + 10)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
ProfilePicturesView(state: state, events: group.events)
|
||||
|
||||
GroupDescription
|
||||
|
||||
if let event {
|
||||
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: event.id)) {
|
||||
Text(event.content)
|
||||
.padding([.top], 1)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.top], 6)
|
||||
}
|
||||
}
|
||||
|
||||
let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
|
||||
let test_repost_1 = NostrEvent(id: "", content: test_encoded_post, pubkey: "pk1", kind: 6, tags: [], createdAt: 1)
|
||||
let test_repost_2 = NostrEvent(id: "", content: test_encoded_post, pubkey: "pk2", kind: 6, tags: [], createdAt: 1)
|
||||
let test_reposts = [test_repost_1, test_repost_2]
|
||||
let test_event_group = EventGroup(events: test_reposts)
|
||||
|
||||
struct EventGroupView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
EventGroupView(state: test_damus_state(), event: test_event, group: .repost(test_event_group))
|
||||
.frame(height: 200)
|
||||
.padding()
|
||||
|
||||
EventGroupView(state: test_damus_state(), event: test_event, group: .reaction(test_event_group))
|
||||
.frame(height: 200)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
86
damus/Views/Notifications/NotificationItemView.swift
Normal file
86
damus/Views/Notifications/NotificationItemView.swift
Normal file
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// NotificationItemView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum ShowItem {
|
||||
case show(NostrEvent?)
|
||||
case dontshow(NostrEvent?)
|
||||
}
|
||||
|
||||
func notification_item_event(events: EventCache, notif: NotificationItem) -> ShowItem {
|
||||
switch notif {
|
||||
case .repost(let evid, _):
|
||||
return .dontshow(events.lookup(evid))
|
||||
case .reply(let ev):
|
||||
return .show(ev)
|
||||
case .reaction(let evid, _):
|
||||
return .dontshow(events.lookup(evid))
|
||||
case .event_zap(let evid, _):
|
||||
return .dontshow(events.lookup(evid))
|
||||
case .profile_zap:
|
||||
return .show(nil)
|
||||
}
|
||||
}
|
||||
|
||||
struct NotificationItemView: View {
|
||||
let state: DamusState
|
||||
let item: NotificationItem
|
||||
|
||||
var show_item: ShowItem {
|
||||
notification_item_event(events: state.events, notif: item)
|
||||
}
|
||||
|
||||
func Item(_ ev: NostrEvent?) -> some View {
|
||||
Group {
|
||||
switch item {
|
||||
case .repost(_, let evgrp):
|
||||
EventGroupView(state: state, event: ev, group: .repost(evgrp))
|
||||
|
||||
case .event_zap(_, let zapgrp):
|
||||
EventGroupView(state: state, event: ev, group: .zap(zapgrp))
|
||||
|
||||
case .profile_zap(let grp):
|
||||
EventGroupView(state: state, event: nil, group: .profile_zap(grp))
|
||||
|
||||
case .reaction(_, let evgrp):
|
||||
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
|
||||
|
||||
case .reply(let ev):
|
||||
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: ev.id)) {
|
||||
EventView(damus: state, event: ev, has_action_bar: true)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding([.top,.bottom], 5)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch show_item {
|
||||
case .show(let ev):
|
||||
Item(ev)
|
||||
|
||||
case .dontshow(let ev):
|
||||
if let ev {
|
||||
Item(ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let test_notification_item: NotificationItem = .repost("evid", test_event_group)
|
||||
|
||||
struct NotificationItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NotificationItemView(state: test_damus_state(), item: test_notification_item)
|
||||
}
|
||||
}
|
||||
50
damus/Views/Notifications/NotificationsView.swift
Normal file
50
damus/Views/Notifications/NotificationsView.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// NotificationsView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NotificationsView: View {
|
||||
let state: DamusState
|
||||
@ObservedObject var notifications: NotificationsModel
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { scroller in
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
Color.white.opacity(0)
|
||||
.id("startblock")
|
||||
.frame(height: 5)
|
||||
ForEach(notifications.notifications, id: \.id) { item in
|
||||
NotificationItemView(state: state, item: item)
|
||||
}
|
||||
}
|
||||
.background(GeometryReader { proxy -> Color in
|
||||
DispatchQueue.main.async {
|
||||
handle_scroll_queue(proxy, queue: self.notifications)
|
||||
}
|
||||
return Color.clear
|
||||
})
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.coordinateSpace(name: "scroll")
|
||||
.onReceive(handle_notify(.scroll_to_top)) { notif in
|
||||
let _ = notifications.flush()
|
||||
self.notifications.should_queue = false
|
||||
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
let _ = notifications.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NotificationsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NotificationsView(state: test_damus_state(), notifications: NotificationsModel())
|
||||
}
|
||||
}
|
||||
37
damus/Views/Notifications/ProfilePicturesView.swift
Normal file
37
damus/Views/Notifications/ProfilePicturesView.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// ProfilePicturesView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ProfilePicturesView: View {
|
||||
let state: DamusState
|
||||
let events: [NostrEvent]
|
||||
|
||||
@State var nav_target: String? = nil
|
||||
@State var navigating: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(destination: ProfileView(damus_state: state, pubkey: nav_target ?? ""), isActive: $navigating) {
|
||||
EmptyView()
|
||||
}
|
||||
HStack {
|
||||
ForEach(events.prefix(8)) { ev in
|
||||
ProfilePicView(pubkey: ev.pubkey, size: 32.0, highlight: .none, profiles: state.profiles)
|
||||
.onTapGesture {
|
||||
nav_target = ev.pubkey
|
||||
navigating = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfilePicturesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ProfilePicturesView(state: test_damus_state(), events: [test_event, test_event])
|
||||
}
|
||||
}
|
||||
@@ -36,41 +36,43 @@ struct ParticipantsView: View {
|
||||
Spacer()
|
||||
}
|
||||
VStack {
|
||||
ForEach(originalReferences.pRefs) { participant in
|
||||
let pubkey = participant.id
|
||||
HStack {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
|
||||
if let about = profile?.about {
|
||||
Text(FollowUserView.markdown.process(about))
|
||||
.lineLimit(3)
|
||||
.font(.footnote)
|
||||
ScrollView {
|
||||
ForEach(originalReferences.pRefs) { participant in
|
||||
let pubkey = participant.id
|
||||
HStack {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
|
||||
if let about = profile?.about {
|
||||
Text(FollowUserView.markdown.process(about))
|
||||
.lineLimit(3)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 30))
|
||||
.foregroundColor(references.contains(participant) ? .purple : .gray)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 30))
|
||||
.foregroundColor(references.contains(participant) ? .purple : .gray)
|
||||
}
|
||||
.onTapGesture {
|
||||
if references.contains(participant) {
|
||||
references = references.filter {
|
||||
$0 != participant
|
||||
}
|
||||
} else {
|
||||
.onTapGesture {
|
||||
if references.contains(participant) {
|
||||
// Don't add it twice
|
||||
references = references.filter {
|
||||
$0 != participant
|
||||
}
|
||||
} else {
|
||||
references.append(participant)
|
||||
if references.contains(participant) {
|
||||
// Don't add it twice
|
||||
} else {
|
||||
references.append(participant)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ enum NostrPostResult {
|
||||
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
||||
|
||||
struct PostView: View {
|
||||
@State var post: String = ""
|
||||
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
||||
|
||||
@FocusState var focus: Bool
|
||||
@State var showPrivateKeyWarning: Bool = false
|
||||
@@ -44,7 +44,14 @@ struct PostView: View {
|
||||
if replying_to?.known_kind == .chat {
|
||||
kind = .chat
|
||||
}
|
||||
let content = self.post.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
|
||||
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
|
||||
if let link = attributes[.link] as? String {
|
||||
post.replaceCharacters(in: range, with: link)
|
||||
}
|
||||
}
|
||||
|
||||
let content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
let new_post = NostrPost(content: content, references: references, kind: kind)
|
||||
|
||||
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
||||
@@ -52,14 +59,14 @@ struct PostView: View {
|
||||
if let replying_to {
|
||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||
} else {
|
||||
damus_state.drafts.post = ""
|
||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
||||
var is_post_empty: Bool {
|
||||
return post.allSatisfy { $0.isWhitespace }
|
||||
return post.string.allSatisfy { $0.isWhitespace }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -74,7 +81,7 @@ struct PostView: View {
|
||||
|
||||
if !is_post_empty {
|
||||
Button(NSLocalizedString("Post", comment: "Button to post a note.")) {
|
||||
showPrivateKeyWarning = contentContainsPrivateKey(self.post)
|
||||
showPrivateKeyWarning = contentContainsPrivateKey(self.post.string)
|
||||
|
||||
if !showPrivateKeyWarning {
|
||||
self.send_post()
|
||||
@@ -97,7 +104,7 @@ struct PostView: View {
|
||||
VStack(alignment: .leading) {
|
||||
ZStack(alignment: .topLeading) {
|
||||
|
||||
TextEditor(text: $post)
|
||||
TextViewWrapper(attributedText: $post)
|
||||
.focused($focus)
|
||||
.textInputAutocapitalization(.sentences)
|
||||
.onChange(of: post) { _ in
|
||||
@@ -108,7 +115,7 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if post.isEmpty {
|
||||
if post.string.isEmpty {
|
||||
Text(POST_PLACEHOLDER)
|
||||
.padding(.top, 8)
|
||||
.padding(.leading, 4)
|
||||
@@ -120,7 +127,7 @@ struct PostView: View {
|
||||
}
|
||||
|
||||
// This if-block observes @ for tagging
|
||||
if let searching = get_searching_string(post) {
|
||||
if let searching = get_searching_string(post.string) {
|
||||
VStack {
|
||||
Spacer()
|
||||
UserSearch(damus_state: damus_state, search: searching, post: $post)
|
||||
@@ -130,7 +137,7 @@ struct PostView: View {
|
||||
.onAppear() {
|
||||
if let replying_to {
|
||||
if damus_state.drafts.replies[replying_to] == nil {
|
||||
damus_state.drafts.replies[replying_to] = ""
|
||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
||||
}
|
||||
if let p = damus_state.drafts.replies[replying_to] {
|
||||
post = p
|
||||
@@ -144,10 +151,10 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||
} else if replying_to == nil && damus_state.drafts.post.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
damus_state.drafts.post = ""
|
||||
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -20,7 +20,8 @@ struct SearchedUser: Identifiable {
|
||||
struct UserSearch: View {
|
||||
let damus_state: DamusState
|
||||
let search: String
|
||||
@Binding var post: String
|
||||
|
||||
@Binding var post: NSMutableAttributedString
|
||||
|
||||
var users: [SearchedUser] {
|
||||
guard let contacts = damus_state.contacts.event else {
|
||||
@@ -39,7 +40,26 @@ struct UserSearch: View {
|
||||
guard let pk = bech32_pubkey(user.pubkey) else {
|
||||
return
|
||||
}
|
||||
post = post.replacingOccurrences(of: "@"+search, with: "@"+pk+" ")
|
||||
|
||||
while post.string.last != "@" {
|
||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
||||
}
|
||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
||||
|
||||
|
||||
var tagString = ""
|
||||
if let name = user.profile?.name {
|
||||
tagString = "@\(name)\u{200B} "
|
||||
}
|
||||
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
||||
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
||||
NSAttributedString.Key.link: "@\(pk)"])
|
||||
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||
let mutableString = NSMutableAttributedString()
|
||||
mutableString.append(post)
|
||||
mutableString.append(tagAttributedString)
|
||||
post = mutableString
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +69,7 @@ struct UserSearch: View {
|
||||
|
||||
struct UserSearch_Previews: PreviewProvider {
|
||||
static let search: String = "jb55"
|
||||
@State static var post: String = "some @jb55"
|
||||
@State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
|
||||
|
||||
static var previews: some View {
|
||||
UserSearch(damus_state: test_damus_state(), search: search, post: $post)
|
||||
|
||||
46
damus/Views/Profile/MaybeAnonPfpView.swift
Normal file
46
damus/Views/Profile/MaybeAnonPfpView.swift
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// MaybeAnonPfpView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MaybeAnonPfpView: View {
|
||||
let state: DamusState
|
||||
let is_anon: Bool
|
||||
let pubkey: String
|
||||
|
||||
init(state: DamusState, event: NostrEvent, pubkey: String) {
|
||||
self.state = state
|
||||
self.is_anon = event_is_anonymous(ev: event)
|
||||
self.pubkey = pubkey
|
||||
}
|
||||
|
||||
init(state: DamusState, is_anon: Bool, pubkey: String) {
|
||||
self.state = state
|
||||
self.is_anon = is_anon
|
||||
self.pubkey = pubkey
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if is_anon {
|
||||
Image(systemName: "person.fill.questionmark")
|
||||
.font(.largeTitle)
|
||||
.frame(width: PFP_SIZE, height: PFP_SIZE)
|
||||
} else {
|
||||
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: state.profiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MaybeAnonPfpView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon")
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ struct ProfileName: View {
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 2) {
|
||||
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||
Text(verbatim: "\(prefix)\(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))")
|
||||
.font(.body)
|
||||
.fontWeight(prefix == "@" ? .none : .bold)
|
||||
if let nip05 = current_nip05 {
|
||||
@@ -136,11 +136,11 @@ struct EventProfileName: View {
|
||||
.font(.body.weight(.bold))
|
||||
.padding([.trailing], 2)
|
||||
|
||||
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||
Text(verbatim: "@\(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))")
|
||||
.foregroundColor(Color("DamusMediumGrey"))
|
||||
.font(eventviewsize_to_font(size))
|
||||
} else {
|
||||
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
|
||||
Text(verbatim: "\(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))")
|
||||
.font(eventviewsize_to_font(size))
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,16 @@ func follow_btn_enabled_state(_ fs: FollowState) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
func followersCountString(_ count: Int, locale: Locale = Locale.current) -> String {
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
return String(format: bundle.localizedString(forKey: "followers_count", value: nil, table: nil), locale: locale, count)
|
||||
}
|
||||
|
||||
func relaysCountString(_ count: Int, locale: Locale = Locale.current) -> String {
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
return String(format: bundle.localizedString(forKey: "relays_count", value: nil, table: nil), locale: locale, count)
|
||||
}
|
||||
|
||||
struct EditButton: View {
|
||||
let damus_state: DamusState
|
||||
|
||||
@@ -100,8 +110,6 @@ struct ProfileView: View {
|
||||
static let markdown = Markdown()
|
||||
|
||||
@State private var selected_tab: ProfileTab = .posts
|
||||
@StateObject var profile: ProfileModel
|
||||
@StateObject var followers: FollowersModel
|
||||
@State private var showingEditProfile = false
|
||||
@State var showing_select_wallet: Bool = false
|
||||
@State var is_zoomed: Bool = false
|
||||
@@ -110,6 +118,21 @@ struct ProfileView: View {
|
||||
@State var filter_state : FilterState = .posts
|
||||
@State var yOffset: CGFloat = 0
|
||||
|
||||
@StateObject var profile: ProfileModel
|
||||
@StateObject var followers: FollowersModel
|
||||
|
||||
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
|
||||
self.damus_state = damus_state
|
||||
self._profile = StateObject(wrappedValue: profile)
|
||||
self._followers = StateObject(wrappedValue: followers)
|
||||
}
|
||||
|
||||
init(damus_state: DamusState, pubkey: String) {
|
||||
self.damus_state = damus_state
|
||||
self._profile = StateObject(wrappedValue: ProfileModel(pubkey: pubkey, damus: damus_state))
|
||||
self._followers = StateObject(wrappedValue: FollowersModel(damus_state: damus_state, target: pubkey))
|
||||
}
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.openURL) var openURL
|
||||
@@ -319,7 +342,8 @@ struct ProfileView: View {
|
||||
.foregroundColor(.gray)
|
||||
} else {
|
||||
let followerCount = followers.count!
|
||||
Text("\(Text(verbatim: "\(followerCount)").font(.subheadline.weight(.medium))) \(Text(String(format: Bundle.main.localizedString(forKey: "followers_count", value: nil, table: nil), followerCount)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
|
||||
let noun_text = Text(verbatim: "\(followersCountString(followerCount))").font(.subheadline).foregroundColor(.gray)
|
||||
Text("\(Text("\(followerCount)").font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,7 +367,8 @@ struct ProfileView: View {
|
||||
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
||||
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
|
||||
HStack {
|
||||
Text("\(Text(verbatim: "\(profile.following)").font(.subheadline.weight(.medium))) \(Text("Following", comment: "Part of a larger sentence to describe how many profiles a user is following.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
||||
let noun_text = Text("Following", comment: "Text on the user profile page next to the number of accounts a user is following.").font(.subheadline).foregroundColor(.gray)
|
||||
Text("\(Text("\(profile.following)").font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
||||
}
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
@@ -366,7 +391,8 @@ struct ProfileView: View {
|
||||
|
||||
if let relays = profile.relays {
|
||||
// Only open relay config view if the user is logged in with private key and they are looking at their own profile.
|
||||
let relay_text = Text("\(Text(verbatim: "\(relays.keys.count)").font(.subheadline.weight(.medium))) \(Text(String(format: Bundle.main.localizedString(forKey: "relays_count", value: nil, table: nil), relays.keys.count)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
||||
let noun_text = Text(verbatim: "\(relaysCountString(relays.keys.count))").font(.subheadline).foregroundColor(.gray)
|
||||
let relay_text = Text("\(Text("\(relays.keys.count)").font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
||||
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
||||
NavigationLink(destination: RelayConfigView(state: damus_state)) {
|
||||
relay_text
|
||||
@@ -443,9 +469,7 @@ struct ProfileView: View {
|
||||
struct ProfileView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ds = test_damus_state()
|
||||
let followers = FollowersModel(damus_state: ds, target: ds.pubkey)
|
||||
let profile_model = ProfileModel(pubkey: ds.pubkey, damus: ds)
|
||||
ProfileView(damus_state: ds, profile: profile_model, followers: followers)
|
||||
ProfileView(damus_state: ds, pubkey: ds.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +530,7 @@ struct KeyView: View {
|
||||
.symbolRenderingMode(.palette)
|
||||
}
|
||||
.padding(.leading,4)
|
||||
Text(abbrev_pubkey(bech32, amount: 16))
|
||||
Text(verbatim: "\(abbrev_pubkey(bech32, amount: 16))")
|
||||
.font(.footnote)
|
||||
.foregroundColor(keyColor())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ struct RelayFilterView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text("To filter your \(timeline.rawValue) feed, please choose applicable relays from the list below:", comment: "Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.")
|
||||
Text("Please choose relays from the list below to filter the current feed:", comment: "Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.")
|
||||
.padding()
|
||||
.padding(.top, 20)
|
||||
.padding(.bottom, 0)
|
||||
|
||||
@@ -38,7 +38,7 @@ struct SaveKeysView: View {
|
||||
.foregroundColor(.white)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
Text("This is your account ID, you can give this to your friends so that they can follow you. Click to copy.", comment: "Label to describe that a public key is the user's account ID and what they can do with it.")
|
||||
Text("This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.", comment: "Label to describe that a public key is the user's account ID and what they can do with it.")
|
||||
.foregroundColor(.white)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ struct SideMenuView: View {
|
||||
navLabel(title: NSLocalizedString("Settings", comment: "Sidebar menu label for accessing the app settings"), systemImage: "gear")
|
||||
}
|
||||
}
|
||||
.labelStyle(SideMenuLabelStyle())
|
||||
.padding([.top, .bottom], verticalSpacing)
|
||||
}
|
||||
}
|
||||
@@ -175,6 +176,17 @@ struct SideMenuView: View {
|
||||
.foregroundColor(textColor())
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
struct SideMenuLabelStyle: LabelStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
configuration.icon
|
||||
.frame(width: 24, height: 24)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
configuration.title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Previews_SideMenuView_Previews: PreviewProvider {
|
||||
|
||||
44
damus/Views/TextViewWrapper.swift
Normal file
44
damus/Views/TextViewWrapper.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// TextViewWrapper.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Swift on 2/24/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct TextViewWrapper: UIViewRepresentable {
|
||||
@Binding var attributedText: NSMutableAttributedString
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let textView = UITextView()
|
||||
textView.delegate = context.coordinator
|
||||
textView.font = UIFont.systemFont(ofSize: 18)
|
||||
textView.textColor = UIColor.label
|
||||
let linkAttributes: [NSAttributedString.Key : Any] = [
|
||||
NSAttributedString.Key.foregroundColor: UIColor(Color.accentColor)]
|
||||
textView.linkTextAttributes = linkAttributes
|
||||
return textView
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UITextView, context: Context) {
|
||||
uiView.attributedText = attributedText
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(attributedText: $attributedText)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject, UITextViewDelegate {
|
||||
@Binding var attributedText: NSMutableAttributedString
|
||||
|
||||
init(attributedText: Binding<NSMutableAttributedString>) {
|
||||
_attributedText = attributedText
|
||||
}
|
||||
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,10 @@ struct ThreadV2View: View {
|
||||
navigating: $navigating,
|
||||
selected: false
|
||||
)
|
||||
|
||||
Divider()
|
||||
.padding(.top, 4)
|
||||
.padding(.leading, 25 * 2)
|
||||
}
|
||||
}.background(GeometryReader { geometry in
|
||||
// get the height and width of the EventView view
|
||||
@@ -289,15 +293,20 @@ struct ThreadV2View: View {
|
||||
).id("main")
|
||||
|
||||
// MARK: - Responses of the actual event view
|
||||
ForEach(thread.childEvents, id: \.id) { event in
|
||||
MutedEventView(
|
||||
damus_state: damus,
|
||||
event: event,
|
||||
scroller: reader,
|
||||
nav_target: $nav_target,
|
||||
navigating: $navigating,
|
||||
selected: false
|
||||
)
|
||||
LazyVStack {
|
||||
ForEach(thread.childEvents, id: \.id) { event in
|
||||
MutedEventView(
|
||||
damus_state: damus,
|
||||
event: event,
|
||||
scroller: nil,
|
||||
nav_target: $nav_target,
|
||||
navigating: $navigating,
|
||||
selected: false
|
||||
)
|
||||
|
||||
Divider()
|
||||
.padding([.top], 4)
|
||||
}
|
||||
}
|
||||
}.padding()
|
||||
}.navigationBarTitle(NSLocalizedString("Thread", comment: "Navigation bar title for note thread."))
|
||||
|
||||
@@ -42,6 +42,9 @@ struct InnerTimelineView: View {
|
||||
navigating = true
|
||||
}
|
||||
.padding(.top, 10)
|
||||
|
||||
Divider()
|
||||
.padding([.top], 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct LoadMoreButton: View {
|
||||
Group {
|
||||
if events.queued > 0 {
|
||||
Button(action: click) {
|
||||
Text("Load \(events.queued) more")
|
||||
Text("Load \(events.queued) more", comment: "Button text for loading more events, where the variable is the number of events.")
|
||||
}
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.padding(10)
|
||||
|
||||
@@ -27,17 +27,6 @@ struct TimelineView: View {
|
||||
MainContent
|
||||
}
|
||||
|
||||
func handle_scroll(_ proxy: GeometryProxy) {
|
||||
let offset = -proxy.frame(in: .named("scroll")).origin.y
|
||||
guard offset >= 0 else {
|
||||
return
|
||||
}
|
||||
let val = offset > 0
|
||||
if self.events.should_queue != val {
|
||||
self.events.should_queue = val
|
||||
}
|
||||
}
|
||||
|
||||
var realtime_bar_opacity: Double {
|
||||
colorScheme == .dark ? 0.2 : 0.1
|
||||
}
|
||||
@@ -55,7 +44,7 @@ struct TimelineView: View {
|
||||
.disabled(loading)
|
||||
.background(GeometryReader { proxy -> Color in
|
||||
DispatchQueue.main.async {
|
||||
handle_scroll(proxy)
|
||||
handle_scroll_queue(proxy, queue: self.events)
|
||||
}
|
||||
return Color.clear
|
||||
})
|
||||
@@ -82,3 +71,18 @@ struct TimelineView_Previews: PreviewProvider {
|
||||
}
|
||||
|
||||
|
||||
protocol ScrollQueue {
|
||||
var should_queue: Bool { get }
|
||||
func set_should_queue(_ val: Bool)
|
||||
}
|
||||
|
||||
func handle_scroll_queue(_ proxy: GeometryProxy, queue: ScrollQueue) {
|
||||
let offset = -proxy.frame(in: .named("scroll")).origin.y
|
||||
guard offset >= 0 else {
|
||||
return
|
||||
}
|
||||
let val = offset > 0
|
||||
if queue.should_queue != val {
|
||||
queue.set_should_queue(val)
|
||||
}
|
||||
}
|
||||
|
||||
215
damus/Views/Zaps/CustomizeZapView.swift
Normal file
215
damus/Views/Zaps/CustomizeZapView.swift
Normal file
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// CustomizeZapView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
enum ZapType {
|
||||
case pub
|
||||
case anon
|
||||
case non_zap
|
||||
}
|
||||
|
||||
struct ZapAmountItem: Identifiable, Hashable {
|
||||
let amount: Int
|
||||
let icon: String
|
||||
|
||||
var id: String {
|
||||
return icon
|
||||
}
|
||||
}
|
||||
|
||||
func get_default_zap_amount_item(_ pubkey: String) -> ZapAmountItem {
|
||||
let def = get_default_zap_amount(pubkey: pubkey) ?? 1000
|
||||
return ZapAmountItem(amount: def, icon: "🤙")
|
||||
}
|
||||
|
||||
func get_zap_amount_items(pubkey: String) -> [ZapAmountItem] {
|
||||
let def_item = get_default_zap_amount_item(pubkey)
|
||||
var entries = [
|
||||
ZapAmountItem(amount: 500, icon: "🙂"),
|
||||
ZapAmountItem(amount: 5000, icon: "💜"),
|
||||
ZapAmountItem(amount: 10_000, icon: "😍"),
|
||||
ZapAmountItem(amount: 20_000, icon: "🤩"),
|
||||
ZapAmountItem(amount: 50_000, icon: "🔥"),
|
||||
ZapAmountItem(amount: 100_000, icon: "🚀"),
|
||||
ZapAmountItem(amount: 1_000_000, icon: "🤯"),
|
||||
]
|
||||
entries.append(def_item)
|
||||
|
||||
entries.sort { $0.amount < $1.amount }
|
||||
return entries
|
||||
}
|
||||
|
||||
struct CustomizeZapView: View {
|
||||
let state: DamusState
|
||||
let event: NostrEvent
|
||||
let lnurl: String
|
||||
@State var comment: String
|
||||
@State var custom_amount: String
|
||||
@State var custom_amount_sats: Int?
|
||||
@State var selected_amount: ZapAmountItem
|
||||
@State var zap_type: ZapType
|
||||
@State var invoice: String
|
||||
@State var error: String?
|
||||
@State var showing_wallet_selector: Bool
|
||||
@State var zapping: Bool
|
||||
|
||||
let zap_amounts: [ZapAmountItem]
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
init(state: DamusState, event: NostrEvent, lnurl: String) {
|
||||
self._comment = State(initialValue: "")
|
||||
self.event = event
|
||||
self.zap_amounts = get_zap_amount_items(pubkey: state.pubkey)
|
||||
self._error = State(initialValue: nil)
|
||||
self._invoice = State(initialValue: "")
|
||||
self._showing_wallet_selector = State(initialValue: false)
|
||||
self._custom_amount = State(initialValue: "")
|
||||
self._zap_type = State(initialValue: .pub)
|
||||
let selected = get_default_zap_amount_item(state.pubkey)
|
||||
self._selected_amount = State(initialValue: selected)
|
||||
self._custom_amount_sats = State(initialValue: nil)
|
||||
self._zapping = State(initialValue: false)
|
||||
self.lnurl = lnurl
|
||||
self.state = state
|
||||
}
|
||||
|
||||
var ZapTypePicker: some View {
|
||||
Picker(NSLocalizedString("Zap Type", comment: "Header text to indicate that the picker below it is to choose the type of zap to send."), selection: $zap_type) {
|
||||
Text("Public", comment: "Picker option to indicate that a zap should be sent publicly and identify the user as who sent it.").tag(ZapType.pub)
|
||||
Text("Anonymous", comment: "Picker option to indicate that a zap should be sent anonymously and not identify the user as who sent it.").tag(ZapType.anon)
|
||||
Text("Non-Zap", comment: "Picker option to indicate that sats should be sent to the user's wallet as a regular Lightning payment, not as a zap.").tag(ZapType.non_zap)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
|
||||
var AmountPicker: some View {
|
||||
Picker(NSLocalizedString("Zap Amount", comment: "Title of picker that allows selection of predefined amounts to zap."), selection: $selected_amount) {
|
||||
ForEach(zap_amounts) { entry in
|
||||
let fmt = format_msats_abbrev(Int64(entry.amount) * 1000)
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text("\(entry.icon)")
|
||||
.frame(width: 30)
|
||||
Text("\(fmt)")
|
||||
.frame(width: 50)
|
||||
}
|
||||
.tag(entry)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.wheel)
|
||||
}
|
||||
|
||||
func receive_zap(notif: Notification) {
|
||||
let zap_ev = notif.object as! ZappingEvent
|
||||
guard zap_ev.is_custom else {
|
||||
return
|
||||
}
|
||||
guard zap_ev.event.id == event.id else {
|
||||
return
|
||||
}
|
||||
|
||||
self.zapping = false
|
||||
|
||||
switch zap_ev.type {
|
||||
case .failed(let err):
|
||||
switch err {
|
||||
case .fetching_invoice:
|
||||
self.error = "Error fetching lightning invoice"
|
||||
case .bad_lnurl:
|
||||
self.error = "Invalid lightning address"
|
||||
}
|
||||
break
|
||||
case .got_zap_invoice(let inv):
|
||||
if should_show_wallet_selector(state.pubkey) {
|
||||
self.invoice = inv
|
||||
self.showing_wallet_selector = true
|
||||
} else {
|
||||
open_with_wallet(wallet: get_default_wallet(state.pubkey).model, invoice: inv)
|
||||
self.showing_wallet_selector = false
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
MainContent
|
||||
.sheet(isPresented: $showing_wallet_selector) {
|
||||
SelectWalletView(showingSelectWallet: $showing_wallet_selector, our_pubkey: state.pubkey, invoice: invoice)
|
||||
}
|
||||
.onReceive(handle_notify(.zapping)) { notif in
|
||||
receive_zap(notif: notif)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
var MainContent: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Form {
|
||||
Section(content: {
|
||||
AmountPicker
|
||||
}, header: {
|
||||
Text("Zap Amount in sats", comment: "Header text to indicate that the picker below it is to choose a pre-defined amount of sats to zap.")
|
||||
})
|
||||
|
||||
Section(content: {
|
||||
TextField(String("100000"), text: $custom_amount)
|
||||
.keyboardType(.numberPad)
|
||||
.onReceive(Just(custom_amount)) { newValue in
|
||||
|
||||
if let parsed = handle_string_amount(new_value: newValue) {
|
||||
self.custom_amount = String(parsed)
|
||||
self.custom_amount_sats = parsed
|
||||
}
|
||||
}
|
||||
}, header: {
|
||||
Text("Custom Zap Amount", comment: "Header text to indicate that the text field below it is to enter a custom zap amount.")
|
||||
})
|
||||
.dismissKeyboardOnTap()
|
||||
|
||||
Section(content: {
|
||||
TextField(NSLocalizedString("Awesome post!", comment: "Placeholder text for a comment to send as part of a zap to the user."), text: $comment)
|
||||
}, header: {
|
||||
Text("Comment", comment: "Header text to indicate that the text field below it is a comment that will be used to send as part of a zap to the user.")
|
||||
})
|
||||
.dismissKeyboardOnTap()
|
||||
|
||||
Section(content: {
|
||||
ZapTypePicker
|
||||
}, header: {
|
||||
Text("Zap Type", comment: "Header text to indicate that the picker below it is to choose the type of zap to send.")
|
||||
})
|
||||
|
||||
if zapping {
|
||||
Text("Zapping...", comment: "Text to indicate that the app is in the process of sending a zap.")
|
||||
} else {
|
||||
Button(NSLocalizedString("Zap", comment: "Button to send a zap.")) {
|
||||
let amount = custom_amount_sats ?? selected_amount.amount
|
||||
send_zap(damus_state: state, event: event, lnurl: lnurl, is_custom: true, comment: comment, amount_sats: amount, zap_type: zap_type)
|
||||
self.zapping = true
|
||||
}
|
||||
.zIndex(16)
|
||||
}
|
||||
|
||||
if let error {
|
||||
Text(error)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomizeZapView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CustomizeZapView(state: test_damus_state(), event: test_event, lnurl: "")
|
||||
.frame(width: 400, height: 600)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user