Browse Source

Merge branch 'master' into add-wallet-modal

post-button-style
Suhail Saqan 2 years ago
committed by GitHub
parent
commit
ef85bb0ca9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 62
      CHANGELOG.md
  2. 2
      damus-c/bolt11.c
  3. 8
      damus.xcodeproj/project.pbxproj
  4. 10
      damus/Components/Shimmer.swift
  5. 13
      damus/Models/FollowersModel.swift
  6. 10
      damus/Models/ProfileModel.swift
  7. 79
      damus/Nostr/Nostr.swift
  8. 14
      damus/Nostr/NostrEvent.swift
  9. 6
      damus/Nostr/NostrMetadata.swift
  10. 16
      damus/Util/Constants.swift
  11. 4
      damus/Views/ConfigView.swift
  12. 173
      damus/Views/EditMetadataView.swift
  13. 2
      damus/Views/EventActionBar.swift
  14. 33
      damus/Views/EventDetailView.swift
  15. 8
      damus/Views/FollowingView.swift
  16. 3
      damus/Views/PostView.swift
  17. 76
      damus/Views/ProfilePicView.swift
  18. 81
      damus/Views/ProfileView.swift
  19. 10
      damus/Views/SearchHomeView.swift
  20. 30
      damus/Views/TimelineView.swift

62
CHANGELOG.md

@ -1,4 +1,65 @@
## [0.1.9] - 2022-12-23
### Added
- Added profile edit view
### Changed
- Increase like boop intensity
- Don't auto-load follower count
- Don't fetch followers right away
### Fixed
- Fix crash on some bolt11 invoices
- Fixed issues when refreshing global view
## [0.1.8] - 2022-12-21
### Changed
- Lots of overall design polish (Sam DuBois)
- Added loading shimmering effect (Sam DuBois)
- Show real name next to username in timelines (Sam DuBois)
### Added
- Animated gif are now shown inline and in profile pictures (@futurepaul)
- Added ability to copy and share image (@futurepaul)
- Haptic feedback when liking for that sweet dopamine hit (radixrat)
- Hide private key in config, make it easier to copy keys (Nitesh Balusu)
### Fixed
- Disable autocorrection for username when creating account
- Fixed issues with the post placeholder
- Disable autocorrection on search
- Disable autocorrection on add relay field
- Parse lightning: prefixes on lightning invoice
- Resize images to fill the space
## [0.1.7] - 2022-12-21
### Changed
- Only show inline images from your friends
- Improved look of profile view
### Fixed
- Added ability to dismiss keyboard during account creation
- Fixed crashed on lightning invoices with empty descriptions
- Make dm chat area visible again
[0.1.7]: https://github.com/damus-io/damus/releases/tag/v0.1.7
## [0.1.6] - 2022-10-30
### Added
@ -77,4 +138,3 @@
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2

2
damus-c/bolt11.c

@ -553,7 +553,7 @@ struct bolt11 *bolt11_decode_nosig(const tal_t *ctx, const char *str, u5 **sig,
case 'n':
problem = decode_n(b11, &hu5, &data,
&data_len, data_length,
have_n);
&have_n);
break;
case 'x':

8
damus.xcodeproj/project.pbxproj

@ -129,6 +129,7 @@
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */; };
4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; };
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -306,6 +307,7 @@
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileName.swift; sourceTree = "<group>"; };
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = "<group>"; };
4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -473,6 +475,7 @@
4C216F31286E388800040376 /* DMChatView.swift */,
4C216F33286F5ACD00040376 /* DMView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -845,6 +848,7 @@
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */,
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */,
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
4C06670B28FDE64700038D2A /* damus.c in Sources */,
@ -1039,7 +1043,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 0.1.8;
MARKETING_VERSION = 0.1.9;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@ -1078,7 +1082,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 0.1.8;
MARKETING_VERSION = 0.1.9;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;

10
damus/Components/Shimmer.swift

@ -41,6 +41,7 @@ struct ShimmeringView<Content: View>: View {
_startPoint = .init(wrappedValue: configuration.initialLocation.start)
_endPoint = .init(wrappedValue: configuration.initialLocation.end)
}
var body: some View {
ZStack {
content()
@ -71,7 +72,12 @@ public struct ShimmerModifier: ViewModifier {
public extension View {
func shimmer(configuration: ShimmerConfiguration = .default) -> some View {
modifier(ShimmerModifier(configuration: configuration))
@ViewBuilder func shimmer(configuration: ShimmerConfiguration = .default, _ loading: Bool) -> some View {
if loading {
modifier(ShimmerModifier(configuration: configuration))
} else {
self
}
}
}

13
damus/Models/FollowersModel.swift

@ -12,12 +12,19 @@ class FollowersModel: ObservableObject {
let target: String
var needs_sub: Bool = true
@Published var contacts: [String] = []
@Published var contacts: [String]? = nil
var has_contact: Set<String> = Set()
let sub_id: String = UUID().description
let profiles_id: String = UUID().description
var count_display: String {
guard let contacts = self.contacts else {
return "?"
}
return "\(contacts.count)";
}
init(damus_state: DamusState, target: String) {
self.damus_state = damus_state
self.target = target
@ -49,13 +56,13 @@ class FollowersModel: ObservableObject {
contacts: damus_state.contacts,
pubkey: damus_state.pubkey, ev: ev
)
contacts.append(ev.pubkey)
contacts?.append(ev.pubkey)
has_contact.insert(ev.pubkey)
}
func load_profiles(relay_id: String) {
var filter = NostrFilter.filter_profiles
let authors = find_profiles_to_fetch_pk(profiles: damus_state.profiles, event_pubkeys: contacts)
let authors = find_profiles_to_fetch_pk(profiles: damus_state.profiles, event_pubkeys: contacts ?? [])
if authors.isEmpty {
return
}

10
damus/Models/ProfileModel.swift

@ -7,7 +7,7 @@
import Foundation
class ProfileModel: ObservableObject {
class ProfileModel: ObservableObject, Equatable {
@Published var events: [NostrEvent] = []
@Published var contacts: NostrEvent? = nil
@Published var following: Int = 0
@ -31,6 +31,14 @@ class ProfileModel: ObservableObject {
self.damus = damus
}
static func == (lhs: ProfileModel, rhs: ProfileModel) -> Bool {
return lhs.pubkey == rhs.pubkey
}
func hash(into hasher: inout Hasher) {
hasher.combine(pubkey)
}
func unsubscribe() {
print("unsubscribing from profile \(pubkey) with sub_id \(sub_id)")
damus.pool.unsubscribe(sub_id: sub_id)

79
damus/Nostr/Nostr.swift

@ -7,13 +7,88 @@
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?) {
self.value = [:]
self.name = name
self.display_name = display_name
self.about = about
self.picture = picture
self.website = website
self.lud06 = lud06
self.lud16 = lud16
self.nip05 = nip05
}
var display_name: String? {
get { return value["display_name"]; }
set(s) { value["display_name"] = s }
}
var name: String? {
get { return value["name"]; }
set(s) { value["name"] = s }
}
var about: String? {
get { return value["about"]; }
set(s) { value["about"] = s }
}
var picture: String? {
get { return value["picture"]; }
set(s) { value["picture"] = s }
}
var website: String? {
get { return value["website"]; }
set(s) { value["website"] = s }
}
var lud06: String? {
get { return value["lud06"]; }
set(s) { value["lud06"] = s }
}
var lud16: String? {
get { return value["lud16"]; }
set(s) { value["lud16"] = s }
}
var nip05: String? {
get { return value["nip05"]; }
set(s) { value["nip05"] = s }
}
var lightning_uri: URL? {
return make_ln_url(self.lud06) ?? make_ln_url(self.lud16)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.value = try container.decode([String: String].self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
static func displayName(profile: Profile?, pubkey: String) -> String {
return profile?.name ?? abbrev_pubkey(pubkey)
}
}
/*
struct Profile: Decodable {
let name: String?
let display_name: String?
let about: String?
let picture: String?
let website: String?
let nip05: String?
let lud06: String?
let lud16: String?
@ -25,6 +100,7 @@ struct Profile: Decodable {
return profile?.name ?? abbrev_pubkey(pubkey)
}
}
*/
func make_ln_url(_ str: String?) -> URL? {
return str.flatMap { URL(string: "lightning:" + $0) }
@ -34,6 +110,3 @@ struct NostrSubscription {
let sub_id: String
let filter: NostrFilter
}

14
damus/Nostr/NostrEvent.swift

@ -275,6 +275,20 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable {
self.created_at = Int64(Date().timeIntervalSince1970)
}
/// Intiialization statement used to specificy ID
///
/// This is mainly used for contant and testing data
init(id: String, content: String, pubkey: String, kind: Int = 1, tags: [[String]] = []) {
self.id = id
self.sig = ""
self.content = content
self.pubkey = pubkey
self.kind = kind
self.tags = tags
self.created_at = Int64(Date().timeIntervalSince1970)
}
init(from: NostrEvent, content: String? = nil) {
self.id = from.id
self.sig = from.sig

6
damus/Nostr/NostrMetadata.swift

@ -13,8 +13,12 @@ struct NostrMetadata: Codable {
let name: String?
let about: String?
let website: String?
let nip05: String?
let picture: 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)
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, lud06: nil, lud16: nil)
}

16
damus/Util/Constants.swift

@ -14,13 +14,15 @@ public class Constants {
static let EXAMPLE_DEMOS = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: PUB_KEY, privkey: "privkey"), likes: EventCounter(our_pubkey: PUB_KEY), boosts: EventCounter(our_pubkey: PUB_KEY), contacts: Contacts(), tips: TipCounter(our_pubkey: PUB_KEY), profiles: Profiles(), dms: DirectMessagesModel())
static let EXAMPLE_EVENTS = [
NostrEvent(content: "Icecream", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "This is a test for a really long note that somebody sent because they thought they were super cool or maybe they were just really excited to share something with the world.", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Bonjour Le Monde", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Why am I helping on this app? Because it's fun!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "PIzza", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Nostr - Damus... Haha get it?", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Nostr - Damus... Haha get it? Bonjour Le Monde mon Ami! C'est la tres importante", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "This is a test for a really long note that somebody sent because they thought they were super cool or maybe they were just really excited to share something with the world.", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Bonjour Le Monde", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Why am I helping on this app? Because it's fun!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Pizza and Icecream! Pizza and Icecream! Testing Testing! 1 .. 2.. 3..", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Nostr - Damus... Haha get it?", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(id: UUID().description, content: "Bonjour Le Monde", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
]
static let WALLETS = """

4
damus/Views/ConfigView.swift

@ -154,6 +154,8 @@ struct ConfigView: View {
struct ConfigView_Previews: PreviewProvider {
static var previews: some View {
ConfigView(state: test_damus_state())
NavigationView {
ConfigView(state: test_damus_state())
}
}
}

173
damus/Views/EditMetadataView.swift

@ -0,0 +1,173 @@
//
// EditMetadataView.swift
// damus
//
// Created by Thomas Tastet on 23/12/2022.
//
import SwiftUI
let PPM_SIZE: CGFloat = 80.0
func isHttpsUrl(_ string: String) -> Bool {
let urlRegEx = "^https://.*$"
let urlTest = NSPredicate(format:"SELF MATCHES %@", urlRegEx)
return urlTest.evaluate(with: string)
}
func isImage(_ urlString: String) -> Bool {
let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
guard let url = URL(string: urlString) else {
return false
}
var result = false
let semaphore = DispatchSemaphore(value: 0)
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
semaphore.signal()
return
}
guard let httpResponse = response as? HTTPURLResponse,
let contentType = httpResponse.allHeaderFields["Content-Type"] as? String else {
semaphore.signal()
return
}
if imageTypes.contains(contentType.lowercased()) {
result = true
}
semaphore.signal()
}
task.resume()
semaphore.wait()
return result
}
struct EditMetadataView: View {
let damus_state: DamusState
@State var display_name: String
@State var about: String
@State var picture: String
@State var nip05: String
@State var name: String
@State var ln: String
@State var website: String
@Environment(\.dismiss) var dismiss
init (damus_state: DamusState) {
self.damus_state = damus_state
let data = damus_state.profiles.lookup(id: damus_state.pubkey)
_name = State(initialValue: data?.name ?? "")
_display_name = State(initialValue: data?.display_name ?? "")
_about = State(initialValue: data?.about ?? "")
_website = State(initialValue: data?.website ?? "")
_picture = State(initialValue: data?.picture ?? "")
_nip05 = State(initialValue: data?.nip05 ?? "")
_ln = State(initialValue: data?.lud16 ?? data?.lud06 ?? "")
}
func save() {
let metadata = NostrMetadata(
display_name: display_name,
name: name,
about: about,
website: website,
nip05: nip05.isEmpty ? nil : nip05,
picture: picture.isEmpty ? nil : picture,
lud06: ln.contains("@") ? ln : nil,
lud16: ln.contains("@") ? nil : ln
);
let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
if let metadata_ev = m_metadata_ev {
damus_state.pool.send(.event(metadata_ev))
}
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Spacer()
InnerProfilePicView(url: URL(string: picture), pubkey: damus_state.pubkey, size: PPM_SIZE, highlight: .none)
Spacer()
}
Form {
Section("Your Name") {
TextField("Satoshi Nakamoto", text: $display_name)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section("Username") {
TextField("satoshi", text: $name)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section ("Profile Picture") {
TextField("https://example.com/pic.jpg", text: $picture)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section("Website") {
TextField("https://jb55.com", text: $website)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section("About Me") {
ZStack(alignment: .topLeading) {
TextEditor(text: $about)
.textInputAutocapitalization(.sentences)
if about.isEmpty {
Text("Absolute boss")
.offset(x: 0, y: 7)
.foregroundColor(Color(uiColor: .placeholderText))
}
}
}
Section("Bitcoin Lightning Tips") {
TextField("Lightning Address or LNURL", text: $ln)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section(content: {
TextField("example.com", text: $nip05)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}, header: {
Text("NIP-05 Verification")
}, footer: {
Text("\(name)@\(nip05) will be used for verification")
})
Button("Save") {
save()
dismiss()
}
}
}
.navigationTitle("Edit Profile")
}
}
struct EditMetadataView_Previews: PreviewProvider {
static var previews: some View {
EditMetadataView(damus_state: test_damus_state())
}
}

2
damus/Views/EventActionBar.swift

@ -21,7 +21,7 @@ enum ActionBarSheet: Identifiable {
struct EventActionBar: View {
let damus_state: DamusState
let event: NostrEvent
let generator = UIImpactFeedbackGenerator(style: .light)
let generator = UIImpactFeedbackGenerator(style: .medium)
@State var sheet: ActionBarSheet? = nil
@State var confirm_boost: Bool = false
@StateObject var bar: ActionBarModel

33
damus/Views/EventDetailView.swift

@ -308,39 +308,6 @@ func scroll_to_event(scroller: ScrollViewProxy, id: String, delay: Double, anima
}
}
/*
func OldEventView(proxy: ScrollViewProxy, ev: NostrEvent, highlight: Highlight, collapsed_events: [CollapsedEvent]) -> some View {
Group {
if ev.id == thread.event.id {
EventView(event: ev, highlight: .main, has_action_bar: true)
.onAppear() {
scroll_to_event(scroller: proxy, id: ev.id, delay: 0.5, animate: true)
}
.onTapGesture {
print_event(ev)
let any = any_collapsed(collapsed_events)
if (collapsed && any) || (!collapsed && !any) {
toggle_collapse_thread(scroller: proxy, id: ev.id)
}
}
} else {
if !(self.collapsed && highlight.is_none) {
EventView(event: ev, highlight: collapsed ? .none : highlight, has_action_bar: true)
.onTapGesture {
print_event(ev)
if !collapsed {
toggle_collapse_thread(scroller: proxy, id: ev.id)
}
thread.event = ev
}
}
}
}
}
*/
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.

8
damus/Views/FollowingView.swift

@ -47,12 +47,18 @@ struct FollowersView: View {
let profile = damus_state.profiles.lookup(id: whos)
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(followers.contacts, id: \.self) { pk in
ForEach(followers.contacts ?? [], id: \.self) { pk in
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
}
}
}
.navigationBarTitle("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers")
.onAppear {
followers.subscribe()
}
.onDisappear {
followers.unsubscribe()
}
}
}

3
damus/Views/PostView.swift

@ -77,8 +77,9 @@ struct PostView: View {
if post.isEmpty {
Text(POST_PLACEHOLDER)
.padding(.top, 8)
.padding(.leading, 10)
.padding(.leading, 4)
.foregroundColor(Color(uiColor: .placeholderText))
.allowsHitTesting(false)
}
}
}

76
damus/Views/ProfilePicView.swift

@ -32,13 +32,13 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
}
}
struct ProfilePicView: View {
struct InnerProfilePicView: View {
@Environment(\.redactionReasons) private var reasons
let url: URL?
let pubkey: String
let size: CGFloat
let highlight: Highlight
let profiles: Profiles
@State var picture: String? = nil
var PlaceholderColor: Color {
return id_to_color(pubkey)
@ -52,30 +52,50 @@ struct ProfilePicView: View {
.padding(2)
}
var MainContent: some View {
var body: some View {
Group {
let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey)
let url = URL(string: pic)
KFAnimatedImage(url)
.configure { view in
view.framePreloadCount = 1
}
.placeholder { _ in
Placeholder
}
.cacheOriginalImage()
.scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously()
.fade(duration: 0.1)
.frame(width: size, height: size)
.clipShape(Circle())
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
if reasons.isEmpty {
KFAnimatedImage(url)
.configure { view in
view.framePreloadCount = 1
}
.placeholder { _ in
Placeholder
}
.cacheOriginalImage()
.scaleFactor(UIScreen.main.scale)
.loadDiskFileSynchronously()
.fade(duration: 0.1)
} else {
KFImage(url)
}
}
.frame(width: size, height: size)
.clipShape(Circle())
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
}
}
struct ProfilePicView: View {
let pubkey: String
let size: CGFloat
let highlight: Highlight
let profiles: Profiles
@State var picture: String?
init (pubkey: String, size: CGFloat, highlight: Highlight, profiles: Profiles, picture: String? = nil) {
self.pubkey = pubkey
self.profiles = profiles
self.size = size
self.highlight = highlight
self._picture = State(initialValue: picture)
}
var body: some View {
MainContent
InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: pubkey, profiles: profiles), pubkey: pubkey, size: size, highlight: highlight)
.onReceive(handle_notify(.profile_updated)) { notif in
let updated = notif.object as! ProfileUpdate
@ -90,10 +110,18 @@ struct ProfilePicView: View {
}
}
func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> URL {
let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey)
if let url = URL(string: pic) {
return url
}
return URL(string: robohash(pubkey))!
}
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)
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 ts_profile = TimestampedProfile(profile: profile, timestamp: 0)
profiles.add(id: pubkey, profile: ts_profile)
return profiles

81
damus/Views/ProfileView.swift

@ -77,12 +77,47 @@ struct ProfileNameView: View {
}
}
struct EditButton: View {
let damus_state: DamusState
@Environment(\.colorScheme) var colorScheme
var body: some View {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
Text("Edit")
.padding(.horizontal, 25)
.padding(.vertical, 10)
.font(.caption.weight(.bold))
.foregroundColor(fillColor())
.background(emptyColor())
.cornerRadius(20)
.overlay {
RoundedRectangle(cornerRadius: 16)
.stroke(borderColor(), lineWidth: 1)
}
}
}
func fillColor() -> Color {
colorScheme == .light ? .black : .white
}
func emptyColor() -> Color {
colorScheme == .light ? .white : .black
}
func borderColor() -> Color {
colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)
}
}
struct ProfileView: View {
let damus_state: DamusState
@State private var selected_tab: ProfileTab = .posts
@StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel
@State private var showingEditProfile = false
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@ -127,7 +162,18 @@ struct ProfileView: View {
DMButton
FollowButtonView(target: profile.get_follow_target(), follow_state: damus_state.contacts.follow_state(profile.pubkey))
if profile.pubkey != damus_state.pubkey {
FollowButtonView(
target: profile.get_follow_target(),
follow_state: damus_state.contacts.follow_state(profile.pubkey)
)
} else {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
EditButton(damus_state: damus_state)
}
}
}
ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts)
@ -155,20 +201,33 @@ struct ProfileView: View {
}
let fview = FollowersView(damus_state: damus_state, whos: profile.pubkey)
.environmentObject(followers)
NavigationLink(destination: fview) {
HStack {
Text("\(followers.contacts.count)")
.font(.subheadline.weight(.medium))
Text("Followers")
.font(.subheadline)
.foregroundColor(.gray)
if followers.contacts != nil {
NavigationLink(destination: fview) {
FollowersCount
}
.buttonStyle(PlainButtonStyle())
} else {
FollowersCount
.onTapGesture {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
followers.contacts = []
followers.subscribe()
}
}
.buttonStyle(PlainButtonStyle())
}
}
}
var FollowersCount: some View {
HStack {
Text("\(followers.count_display)")
.font(.subheadline.weight(.medium))
Text("Followers")
.font(.subheadline)
.foregroundColor(.gray)
}
}
var body: some View {
VStack(alignment: .leading) {
ScrollView {
@ -187,7 +246,7 @@ struct ProfileView: View {
}
.onAppear() {
profile.subscribe()
followers.subscribe()
//followers.subscribe()
}
.onDisappear {
profile.unsubscribe()
@ -211,7 +270,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(), tips: TipCounter(our_pubkey: pubkey), profiles: Profiles(), dms: DirectMessagesModel())
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")
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 tsprof = TimestampedProfile(profile: prof, timestamp: 0)
damus.profiles.add(id: pubkey, profile: tsprof)
return damus

10
damus/Views/SearchHomeView.swift

@ -42,7 +42,8 @@ struct SearchHomeView: View {
var GlobalContent: some View {
return TimelineView(events: $model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true })
.refreshable {
// Fetch new information by resubscribing to the relay
// Fetch new information by unsubscribing and resubscribing to the relay
model.unsubscribe()
model.subscribe()
}
}
@ -50,7 +51,8 @@ struct SearchHomeView: View {
var SearchContent: some View {
SearchResultsView(damus_state: damus_state, search: $search)
.refreshable {
// Fetch new information by resubscribing to the relay
// Fetch new information by unsubscribing and resubscribing to the relay
model.unsubscribe()
model.subscribe()
}
}
@ -68,9 +70,7 @@ struct SearchHomeView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
MainContent
}
MainContent
.safeAreaInset(edge: .top) {
VStack(spacing: 0) {
SearchInput

30
damus/Views/TimelineView.swift

@ -40,25 +40,6 @@ struct InnerTimelineView: View {
}
}
struct InnerTimelineRedactedView: View {
let events: [NostrEvent]
let damus: DamusState
let show_friend_icon: Bool
var body: some View {
VStack {
ForEach(events, id: \.id) { event in
EventView(event: event, highlight: .none, has_action_bar: true, damus: damus, show_friend_icon: show_friend_icon)
.buttonStyle(PlainButtonStyle())
}
}
.shimmer()
.redacted(reason: .placeholder)
.padding(.horizontal)
.disabled(true)
}
}
struct TimelineView: View {
@Binding var events: [NostrEvent]
@ -75,13 +56,10 @@ struct TimelineView: View {
var MainContent: some View {
ScrollViewReader { scroller in
ScrollView {
if loading {
InnerTimelineRedactedView(events: Constants.EXAMPLE_EVENTS, damus: damus, show_friend_icon: true)
ProgressView()
.progressViewStyle(.circular)
} else {
InnerTimelineView(events: $events, damus: damus, show_friend_icon: show_friend_icon, filter: filter)
}
InnerTimelineView(events: loading ? .constant(Constants.EXAMPLE_EVENTS) : $events, damus: damus, show_friend_icon: show_friend_icon, filter: loading ? { _ in true } : filter)
.redacted(reason: loading ? .placeholder : [])
.shimmer(loading)
.disabled(loading)
}
.onReceive(NotificationCenter.default.publisher(for: .scroll_to_top)) { _ in
guard let event = events.filter(self.filter).first else {

Loading…
Cancel
Save