commit 9d44ed0bfee34849b6545538224f9eb68609ebca
parent 33383265c813a226d2b2a4049044793358477f31
Author: Jason Jōb <jason.allan.job@gmail.com>
Date: Tue, 10 Jan 2023 15:12:34 -0800
Profile Banner Images
Changelog-Added: Profile banner images
Closes: #302
Diffstat:
8 files changed, 151 insertions(+), 15 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -147,6 +147,7 @@
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
+ 9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
@@ -347,6 +348,7 @@
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
+ 9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
@@ -530,6 +532,7 @@
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */,
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */,
647D9A8C2968520300A295DE /* SideMenuView.swift */,
+ 9609F057296E220800069BF3 /* BannerImageView.swift */,
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */,
);
path = Views;
@@ -904,6 +907,7 @@
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
+ 9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
4C363A94282704FA006E126D /* Post.swift in Sources */,
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -578,6 +578,13 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
}
}
+ let banner = tprof.profile.banner ?? ""
+ if let _ = URL(string: banner) {
+ DispatchQueue.main.async {
+ notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
+ }
+ }
+
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
diff --git a/damus/Nostr/Nostr.swift b/damus/Nostr/Nostr.swift
@@ -10,12 +10,13 @@ import Foundation
struct Profile: Codable {
var value: [String: String]
- init (name: String?, display_name: String?, about: String?, picture: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
+ init (name: String?, display_name: String?, about: String?, picture: String?, banner: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
self.value = [:]
self.name = name
self.display_name = display_name
self.about = about
self.picture = picture
+ self.banner = banner
self.website = website
self.lud06 = lud06
self.lud16 = lud16
@@ -42,6 +43,11 @@ struct Profile: Codable {
set(s) { value["picture"] = s }
}
+ var banner: String? {
+ get { return value["banner"]; }
+ set(s) { value["banner"] = s }
+ }
+
var website: String? {
get { return value["website"]; }
set(s) { value["website"] = s }
@@ -95,7 +101,7 @@ struct Profile: Codable {
}
func make_test_profile() -> Profile {
- return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com")
+ return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com")
}
func make_ln_url(_ str: String?) -> URL? {
diff --git a/damus/Nostr/NostrMetadata.swift b/damus/Nostr/NostrMetadata.swift
@@ -15,10 +15,11 @@ struct NostrMetadata: Codable {
let website: String?
let nip05: String?
let picture: String?
+ let banner: String?
let lud06: String?
let lud16: String?
}
func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
- return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, lud06: nil, lud16: nil)
+ return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, banner: nil, lud06: nil, lud16: nil)
}
diff --git a/damus/Views/BannerImageView.swift b/damus/Views/BannerImageView.swift
@@ -0,0 +1,85 @@
+//
+// BannerImageView.swift
+// damus
+//
+// Created by Jason Jōb on 2023-01-10.
+//
+
+import SwiftUI
+import Kingfisher
+
+struct InnerBannerImageView: View {
+ let url: URL?
+ let pubkey: String
+
+ var body: some View {
+ ZStack {
+ Color(uiColor: .systemBackground)
+
+ if (url != nil) {
+ KFAnimatedImage(url)
+ .callbackQueue(.dispatch(.global(qos: .background)))
+ .processingQueue(.dispatch(.global(qos: .background)))
+ .appendProcessor(LargeImageProcessor.shared)
+ .configure { view in
+ view.framePreloadCount = 1
+ }
+ .placeholder { _ in
+ Image("profile-banner").resizable()
+ }
+ .scaleFactor(UIScreen.main.scale)
+ .loadDiskFileSynchronously()
+ .fade(duration: 0.1)
+ } else {
+ Image("profile-banner").resizable()
+ }
+ }
+ }
+}
+
+struct BannerImageView: View {
+ let pubkey: String
+ let profiles: Profiles
+
+ @State var banner: String?
+
+ init (pubkey: String, profiles: Profiles, banner: String? = nil) {
+ self.pubkey = pubkey
+ self.profiles = profiles
+ self._banner = State(initialValue: banner)
+ }
+
+ var body: some View {
+ InnerBannerImageView(url: get_banner_url(banner: banner, pubkey: pubkey, profiles: profiles), pubkey: pubkey)
+ .onReceive(handle_notify(.profile_updated)) { notif in
+ let updated = notif.object as! ProfileUpdate
+
+ guard updated.pubkey == self.pubkey else {
+ return
+ }
+
+ if let bannerImage = updated.profile.banner {
+ self.banner = bannerImage
+ }
+ }
+ }
+}
+
+func get_banner_url(banner: String?, pubkey: String, profiles: Profiles) -> URL? {
+ let bannerUrlString = banner ?? profiles.lookup(id: pubkey)?.banner ?? ""
+ if let url = URL(string: bannerUrlString) {
+ return url
+ }
+ return nil
+}
+
+struct BannerImageView_Previews: PreviewProvider {
+ static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
+
+ static var previews: some View {
+ BannerImageView(
+ pubkey: pubkey,
+ profiles: make_preview_profiles(pubkey))
+ }
+}
+
diff --git a/damus/Views/EditMetadataView.swift b/damus/Views/EditMetadataView.swift
@@ -8,6 +8,7 @@
import SwiftUI
let PPM_SIZE: CGFloat = 80.0
+let BANNER_HEIGHT: CGFloat = 150.0;
func isHttpsUrl(_ string: String) -> Bool {
let urlRegEx = "^https://.*$"
@@ -56,12 +57,14 @@ struct EditMetadataView: View {
@State var display_name: String
@State var about: String
@State var picture: String
+ @State var banner: String
@State var nip05: String
@State var name: String
@State var ln: String
@State var website: String
@Environment(\.dismiss) var dismiss
+ @Environment(\.colorScheme) var colorScheme
init (damus_state: DamusState) {
self.damus_state = damus_state
@@ -72,10 +75,15 @@ struct EditMetadataView: View {
_about = State(initialValue: data?.about ?? "")
_website = State(initialValue: data?.website ?? "")
_picture = State(initialValue: data?.picture ?? "")
+ _banner = State(initialValue: data?.banner ?? "")
_nip05 = State(initialValue: data?.nip05 ?? "")
_ln = State(initialValue: data?.lud16 ?? data?.lud06 ?? "")
}
+ func imageBorderColor() -> Color {
+ colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
+ }
+
func save() {
let metadata = NostrMetadata(
display_name: display_name,
@@ -84,6 +92,7 @@ struct EditMetadataView: View {
website: website,
nip05: nip05.isEmpty ? nil : nip05,
picture: picture.isEmpty ? nil : picture,
+ banner: banner.isEmpty ? nil : banner,
lud06: ln.contains("@") ? nil : ln,
lud16: ln.contains("@") ? ln : nil
);
@@ -99,13 +108,32 @@ struct EditMetadataView: View {
return NIP05.parse(nip05)
}
+ var TopSection: some View {
+ ZStack(alignment: .top) {
+ GeometryReader { geo in
+ BannerImageView(pubkey: damus_state.pubkey, profiles: damus_state.profiles)
+ .aspectRatio(contentMode: .fill)
+ .frame(width: geo.size.width, height: BANNER_HEIGHT)
+ .clipped()
+ }.frame(height: BANNER_HEIGHT)
+ VStack(alignment: .leading) {
+ let pfp_size: CGFloat = 90.0
+
+ HStack(alignment: .center) {
+ ProfilePicView(pubkey: damus_state.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
+ .offset(y: -(pfp_size/2.0)) // Increase if set a frame
+
+ Spacer()
+ }.padding(.bottom,-(pfp_size/2.0))
+ }
+ .padding(.horizontal,18)
+ .padding(.top,BANNER_HEIGHT)
+ }
+ }
+
var body: some View {
VStack(alignment: .leading) {
- HStack {
- Spacer()
- InnerProfilePicView(url: URL(string: picture), fallbackUrl: nil, pubkey: damus_state.pubkey, size: PPM_SIZE, highlight: .none)
- Spacer()
- }
+ TopSection
Form {
Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
TextField("Satoshi Nakamoto", text: $display_name)
@@ -126,6 +154,12 @@ struct EditMetadataView: View {
.textInputAutocapitalization(.never)
}
+ Section (NSLocalizedString("Banner Image", comment: "Label for Banner Image section of user profile form.")) {
+ TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $banner)
+ .autocorrectionDisabled(true)
+ .textInputAutocapitalization(.never)
+ }
+
Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
.autocorrectionDisabled(true)
@@ -172,7 +206,7 @@ struct EditMetadataView: View {
}
}
}
- .navigationTitle(NSLocalizedString("Edit Profile", comment: "Title of navigation view for Edit Profile."))
+ .ignoresSafeArea()
}
}
diff --git a/damus/Views/ProfilePicView.swift b/damus/Views/ProfilePicView.swift
@@ -182,7 +182,7 @@ func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> UR
func make_preview_profiles(_ pubkey: String) -> Profiles {
let profiles = Profiles()
let picture = "http://cdn.jb55.com/img/red-me.jpg"
- let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com")
+ let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com")
let ts_profile = TimestampedProfile(profile: profile, timestamp: 0)
profiles.add(id: pubkey, profile: ts_profile)
return profiles
diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift
@@ -194,15 +194,14 @@ struct ProfileView: View {
var TopSection: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
- Image("profile-banner")
- .resizable()
+ BannerImageView(pubkey: damus_state.pubkey, profiles: damus_state.profiles)
.aspectRatio(contentMode: .fill)
- .frame(width: geo.size.width, height: 150)
+ .frame(width: geo.size.width, height: BANNER_HEIGHT)
.clipped()
ShareButton
.offset(x: geo.size.width - 80.0, y: 50.0 )
- }
+ }.frame(height: BANNER_HEIGHT)
VStack(alignment: .leading) {
let data = damus_state.profiles.lookup(id: profile.pubkey)
let pfp_size: CGFloat = 90.0
@@ -358,7 +357,7 @@ func test_damus_state() -> DamusState {
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
let damus = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: pubkey, privkey: "privkey"), likes: EventCounter(our_pubkey: pubkey), boosts: EventCounter(our_pubkey: pubkey), contacts: Contacts(our_pubkey: pubkey), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel(), previews: PreviewCache())
- let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io")
+ 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")
let tsprof = TimestampedProfile(profile: prof, timestamp: 0)
damus.profiles.add(id: pubkey, profile: tsprof)
return damus