commit bffa42a13a5e799e3083bdb1834fd50657c0448e
parent 8097cfdfb8e6fe328e0a2683ec24b6d31edd7dc9
Author: William Casarin <jb55@jb55.com>
Date: Mon, 15 May 2023 11:57:37 -0700
Supporter Badges
Diffstat:
14 files changed, 194 insertions(+), 12 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -53,6 +53,8 @@
4C216F34286F5ACD00040376 /* DMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F33286F5ACD00040376 /* DMView.swift */; };
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */; };
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F372871EDE300040376 /* DirectMessageModel.swift */; };
+ 4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */; };
+ 4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2859612A12A7F0004746F7 /* GoldSupportGradient.swift */; };
4C285C8228385570008A31F1 /* CarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8128385570008A31F1 /* CarouselView.swift */; };
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8328385690008A31F1 /* CreateAccountView.swift */; };
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */; };
@@ -444,6 +446,8 @@
4C216F33286F5ACD00040376 /* DMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMView.swift; sourceTree = "<group>"; };
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDismissKeyboard.swift; sourceTree = "<group>"; };
4C216F372871EDE300040376 /* DirectMessageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessageModel.swift; sourceTree = "<group>"; };
+ 4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupporterBadge.swift; sourceTree = "<group>"; };
+ 4C2859612A12A7F0004746F7 /* GoldSupportGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoldSupportGradient.swift; sourceTree = "<group>"; };
4C285C8128385570008A31F1 /* CarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselView.swift; sourceTree = "<group>"; };
4C285C8328385690008A31F1 /* CreateAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountView.swift; sourceTree = "<group>"; };
4C285C85283892E7008A31F1 /* CreateAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountModel.swift; sourceTree = "<group>"; };
@@ -1041,6 +1045,7 @@
children = (
4C7D09712A0AEF5E00943473 /* DamusGradient.swift */,
4C7D09732A0AEF9000943473 /* AlbyGradient.swift */,
+ 4C2859612A12A7F0004746F7 /* GoldSupportGradient.swift */,
);
path = Gradients;
sourceTree = "<group>";
@@ -1218,6 +1223,7 @@
4CE4F0F729DB7399005914DB /* ThiccDivider.swift */,
4C1A9A2229DDDB8100516EAC /* IconLabel.swift */,
4C8D00C929DF80350036AF10 /* TruncatedText.swift */,
+ 4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */,
);
path = Components;
sourceTree = "<group>";
@@ -1730,6 +1736,7 @@
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */,
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
+ 4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
@@ -1837,6 +1844,7 @@
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
+ 4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/damus/Components/Gradients/GoldSupportGradient.swift b/damus/Components/Gradients/GoldSupportGradient.swift
@@ -0,0 +1,29 @@
+//
+// GoldSupportGradient.swift
+// damus
+//
+// Created by William Casarin on 2023-05-15.
+//
+
+import SwiftUI
+
+fileprivate let gold_grad_c1 = hex_col(r: 226, g: 168, b: 0)
+fileprivate let gold_grad_c2 = hex_col(r: 249, g: 243, b: 100)
+
+fileprivate let gold_grad = [gold_grad_c2, gold_grad_c1]
+
+let GoldGradient: LinearGradient =
+ LinearGradient(colors: gold_grad, startPoint: .bottomLeading, endPoint: .topTrailing)
+
+struct GoldGradientView: View {
+ var body: some View {
+ GoldGradient
+ .edgesIgnoringSafeArea([.top,.bottom])
+ }
+}
+
+struct GoldGradientView_Previews: PreviewProvider {
+ static var previews: some View {
+ GoldGradientView()
+ }
+}
diff --git a/damus/Components/SupporterBadge.swift b/damus/Components/SupporterBadge.swift
@@ -0,0 +1,73 @@
+//
+// SupporterBadge.swift
+// damus
+//
+// Created by William Casarin on 2023-05-15.
+//
+
+import SwiftUI
+
+struct SupporterBadge: View {
+ let percent: Int
+
+ let size: CGFloat = 17
+
+ var body: some View {
+ if percent < 100 {
+ Image("star.fill")
+ .resizable()
+ .frame(width:size, height:size)
+ .foregroundColor(support_level_color(percent))
+ } else {
+ Image("star.fill")
+ .resizable()
+ .frame(width:size, height:size)
+ .foregroundStyle(GoldGradient)
+ }
+ }
+}
+
+func support_level_color(_ percent: Int) -> Color {
+ if percent == 0 {
+ return .gray
+ }
+
+ let percent_f = Double(percent) / 100.0
+ let cutoff = 0.5
+ let h = cutoff + (percent_f * cutoff); // Hue (note 0.2 = Green, see huge chart below)
+ let s = 0.9; // Saturation
+ let b = 0.9; // Brightness
+
+ return Color(hue: h, saturation: s, brightness: b)
+}
+
+struct SupporterBadge_Previews: PreviewProvider {
+ static func Level(_ p: Int) -> some View {
+ HStack(alignment: .center) {
+ SupporterBadge(percent: p)
+ .frame(width: 50)
+ Text("\(p)")
+ .frame(width: 50)
+ }
+ }
+
+ static var previews: some View {
+ VStack(spacing: 0) {
+ VStack(spacing: 0) {
+ Level(1)
+ Level(10)
+ Level(20)
+ Level(30)
+ Level(40)
+ Level(50)
+ }
+ Level(60)
+ Level(70)
+ Level(80)
+ Level(90)
+ Level(100)
+ }
+ }
+}
+
+
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -397,7 +397,7 @@ struct ContentView: View {
return
}
ds.postbox.send(ev)
- if let profile = ds.profiles.profiles[ev.pubkey] {
+ if let profile = ds.profiles.lookup_with_timestamp(id: ev.pubkey) {
ds.postbox.send(profile.event)
}
}
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -735,7 +735,7 @@ func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: P
var old_nip05: String? = nil
if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) {
old_nip05 = mprof.profile.nip05
- if mprof.timestamp > ev.created_at {
+ if mprof.event.created_at > ev.created_at {
// skip if we already have an newer profile
return
}
@@ -752,7 +752,7 @@ func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: P
print("validated nip05 for '\(nip05)'")
}
- DispatchQueue.main.async {
+ Task { @MainActor in
profiles.validated[ev.pubkey] = validated
profiles.nip05_pubkey[nip05] = ev.pubkey
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift
@@ -246,7 +246,7 @@ func format_msats_abbrev(_ msats: Int64) -> String {
formatter.positiveSuffix = "m"
formatter.positivePrefix = ""
formatter.minimumFractionDigits = 0
- formatter.maximumFractionDigits = 2
+ formatter.maximumFractionDigits = 3
formatter.roundingMode = .down
formatter.roundingIncrement = 0.1
formatter.multiplier = 1
diff --git a/damus/Models/WalletModel.swift b/damus/Models/WalletModel.swift
@@ -16,6 +16,7 @@ enum WalletConnectState {
class WalletModel: ObservableObject {
var settings: UserSettingsStore
private(set) var previous_state: WalletConnectState
+ var inital_percent: Int
@Published private(set) var connect_state: WalletConnectState
@@ -23,6 +24,7 @@ class WalletModel: ObservableObject {
self.connect_state = state
self.previous_state = .none
self.settings = settings
+ self.inital_percent = settings.donation_percent
}
init(settings: UserSettingsStore) {
@@ -35,6 +37,7 @@ class WalletModel: ObservableObject {
self.previous_state = .none
self.connect_state = .none
}
+ self.inital_percent = settings.donation_percent
}
func cancel() {
diff --git a/damus/Nostr/Profiles.swift b/damus/Nostr/Profiles.swift
@@ -17,7 +17,7 @@ class Profiles {
qos: .userInteractive,
attributes: .concurrent)
- var profiles: [String: TimestampedProfile] = [:]
+ private var profiles: [String: TimestampedProfile] = [:]
var validated: [String: NIP05] = [:]
var nip05_pubkey: [String: String] = [:]
var zappers: [String: String] = [:]
@@ -26,6 +26,12 @@ class Profiles {
return validated[pk]
}
+ func enumerated() -> EnumeratedSequence<[String: TimestampedProfile]> {
+ return queue.sync {
+ return profiles.enumerated()
+ }
+ }
+
func lookup_zapper(pubkey: String) -> String? {
if let zapper = zappers[pubkey] {
return zapper
diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift
@@ -140,7 +140,7 @@ func search_users_for_autocomplete(profiles: Profiles, tags: [[String]], search
}
// search profile cache as well
- for tup in profiles.profiles.enumerated() {
+ for tup in profiles.enumerated() {
let pk = tup.element.key
let prof = tup.element.value.profile
diff --git a/damus/Views/Profile/EventProfileName.swift b/damus/Views/Profile/EventProfileName.swift
@@ -15,6 +15,7 @@ struct EventProfileName: View {
@State var display_name: DisplayName?
@State var nip05: NIP05?
+ @State var donation: Int?
let size: EventViewKind
@@ -23,6 +24,7 @@ struct EventProfileName: View {
self.pubkey = pubkey
self.profile = profile
self.size = size
+ self._donation = State(wrappedValue: profile?.damus_donation)
}
var friend_type: FriendType? {
@@ -45,6 +47,15 @@ struct EventProfileName: View {
return profile.reactions == false
}
+ var supporter: Int? {
+ guard let donation, donation > 0
+ else {
+ return nil
+ }
+
+ return donation
+ }
+
var body: some View {
HStack(spacing: 2) {
switch current_display_name {
@@ -73,6 +84,10 @@ struct EventProfileName: View {
Image("zap-hashtag")
.frame(width: 14, height: 14)
}
+
+ if let supporter {
+ SupporterBadge(percent: supporter)
+ }
}
.onReceive(handle_notify(.profile_updated)) { notif in
let update = notif.object as! ProfileUpdate
@@ -81,6 +96,7 @@ struct EventProfileName: View {
}
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
nip05 = damus_state.profiles.is_validated(pubkey)
+ donation = update.profile.damus_donation
}
}
}
diff --git a/damus/Views/Profile/ProfileName.swift b/damus/Views/Profile/ProfileName.swift
@@ -34,6 +34,7 @@ struct ProfileName: View {
@State var display_name: DisplayName?
@State var nip05: NIP05?
+ @State var donation: Int?
init(pubkey: String, profile: Profile?, damus: DamusState, show_nip5_domain: Bool = true) {
self.pubkey = pubkey
@@ -75,6 +76,17 @@ struct ProfileName: View {
return profile.reactions == false
}
+ var supporter: Int? {
+ guard let profile,
+ let donation = profile.damus_donation,
+ donation > 0
+ else {
+ return nil
+ }
+
+ return donation
+ }
+
var body: some View {
HStack(spacing: 2) {
Text(verbatim: "\(prefix)\(name_choice)")
@@ -90,6 +102,9 @@ struct ProfileName: View {
Image("zap-hashtag")
.frame(width: 14, height: 14)
}
+ if let supporter {
+ SupporterBadge(percent: supporter)
+ }
}
.onReceive(handle_notify(.profile_updated)) { notif in
let update = notif.object as! ProfileUpdate
@@ -98,6 +113,7 @@ struct ProfileName: View {
}
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
nip05 = damus_state.profiles.is_validated(pubkey)
+ donation = profile?.damus_donation
}
}
}
diff --git a/damus/Views/Profile/ProfileView.swift b/damus/Views/Profile/ProfileView.swift
@@ -496,6 +496,9 @@ struct ProfileView_Previews: PreviewProvider {
func test_damus_state() -> DamusState {
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
let damus = DamusState.empty
+ let settings = UserSettingsStore()
+ settings.donation_percent = 100
+ settings.default_zap_amount = 1971
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil)
let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_event)
diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift
@@ -182,7 +182,7 @@ func make_hashtagable(_ str: String) -> String {
func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] {
let new = search.lowercased()
- return profiles.profiles.enumerated().reduce(into: []) { acc, els in
+ return profiles.enumerated().reduce(into: []) { acc, els in
let pk = els.element.key
let prof = els.element.value.profile
diff --git a/damus/Views/Wallet/WalletView.swift b/damus/Views/Wallet/WalletView.swift
@@ -58,7 +58,19 @@ struct WalletView: View {
var tip_msats: String {
let msats = Int64(percent * Double(model.settings.default_zap_amount * 1000))
let s = format_msats_abbrev(msats)
- return s.split(separator: ".").first.map({ x in String(x) }) ?? s
+ // TODO: fix formatting and remove this hack
+ let parts = s.split(separator: ".")
+ if parts.count == 1 {
+ return s
+ }
+ if let end = parts[safe: 1] {
+ if end.allSatisfy({ c in c.isNumber }) {
+ return String(parts[0])
+ } else {
+ return s
+ }
+ }
+ return s
}
var SupportDamus: some View {
@@ -93,6 +105,7 @@ struct WalletView: View {
Text("\(Int(binding.wrappedValue))%")
.font(.title.bold())
.foregroundColor(.white)
+ .frame(width: 80)
}
HStack{
@@ -103,7 +116,7 @@ struct WalletView: View {
Text("\(Image("zap.fill")) \(format_msats_abbrev(Int64(model.settings.default_zap_amount) * 1000))")
.font(.title)
.foregroundColor(percent == 0 ? .gray : .yellow)
- .frame(width: 100)
+ .frame(width: 120)
}
Text("Zap")
@@ -121,9 +134,10 @@ struct WalletView: View {
Text("\(Image("zap.fill")) \(tip_msats)")
.font(.title)
.foregroundColor(percent == 0 ? .gray : Color.yellow)
- .frame(width: 100)
+ .frame(width: 120)
}
- Text("💜")
+
+ Text(percent == 0 ? "🩶" : "💜")
.foregroundColor(.white)
}
Spacer()
@@ -154,16 +168,30 @@ struct WalletView: View {
ConnectWalletView(model: model)
case .existing(let nwc):
MainWalletView(nwc: nwc)
+ .onAppear() {
+ model.inital_percent = settings.donation_percent
+ }
+ .onChange(of: settings.donation_percent) { p in
+ guard let profile = damus_state.profiles.lookup(id: damus_state.pubkey) else {
+ return
+ }
+
+ profile.damus_donation = p
+
+ notify(.profile_updated, ProfileUpdate(pubkey: damus_state.pubkey, profile: profile))
+ }
.onDisappear {
guard let keypair = damus_state.keypair.to_full(),
let profile = damus_state.profiles.lookup(id: damus_state.pubkey),
- profile.damus_donation != settings.donation_percent
+ model.inital_percent != profile.damus_donation
else {
return
}
profile.damus_donation = settings.donation_percent
let meta = make_metadata_event(keypair: keypair, metadata: profile)
+ let tsprofile = TimestampedProfile(profile: profile, timestamp: meta.created_at, event: meta)
+ damus_state.profiles.add(id: damus_state.pubkey, profile: tsprofile)
damus_state.postbox.send(meta)
}
}