mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
Supporter Badges
This commit is contained in:
parent
8097cfdfb8
commit
bffa42a13a
@ -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;
|
||||
};
|
||||
|
29
damus/Components/Gradients/GoldSupportGradient.swift
Normal file
29
damus/Components/Gradients/GoldSupportGradient.swift
Normal file
@ -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()
|
||||
}
|
||||
}
|
73
damus/Components/SupporterBadge.swift
Normal file
73
damus/Components/SupporterBadge.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user