damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit b1d14ff21f7d68b8eb8358993ef90af18651d37b
parent 5dedb7c29d686d94f1a5b3fc1a4293db92e28c84
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 21 Dec 2022 09:46:03 -0800

Merge commit 'f0d242a'

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 17+++++++++++++++++
Mdamus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 9+++++++++
Adamus/Components/Shimmer.swift | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/ContentView.swift | 1+
Adamus/Util/Constants.swift | 25+++++++++++++++++++++++++
Mdamus/Views/EventActionBar.swift | 6+++---
Mdamus/Views/EventView.swift | 20+++++++++++++-------
Mdamus/Views/FollowButtonView.swift | 25++++++++++++++++++++-----
Mdamus/Views/NoteContentView.swift | 2++
Mdamus/Views/ProfileName.swift | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdamus/Views/ProfileView.swift | 13+++++++++----
Mdamus/Views/SearchHomeView.swift | 10+++++++++-
Mdamus/Views/TimelineView.swift | 26++++++++++++++++++++++----
13 files changed, 283 insertions(+), 27 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; }; + 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; + 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; }; 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; }; 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; }; 4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; }; @@ -148,6 +150,8 @@ /* Begin PBXFileReference section */ 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; }; + 3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; + 31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; }; 4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; }; 4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; }; 4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; }; @@ -504,6 +508,7 @@ 4C285C8B28398BC6008A31F1 /* Keys.swift */, 4C90BD19283AA67F008EE7EF /* Bech32.swift */, 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */, + 3169CAEC294FCCFC00EE4006 /* Constants.swift */, ); path = Util; sourceTree = "<group>"; @@ -511,6 +516,7 @@ 4CE4F9DF285287A000C00DD9 /* Components */ = { isa = PBXGroup; children = ( + 31D2E846295218AF006D67F8 /* Shimmer.swift */, 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */, 4CD7641A28A1641400B6928F /* EndBlock.swift */, 4C06670528FCB08600038D2A /* ImageCarousel.swift */, @@ -695,6 +701,7 @@ 4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */, 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */, 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */, + 3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */, ); productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */; projectDirPath = ""; @@ -781,6 +788,7 @@ 4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */, 4C75EFB128049D510006080F /* NostrResponse.swift in Sources */, 4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */, + 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */, 4C285C8228385570008A31F1 /* CarouselView.swift in Sources */, 4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */, 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */, @@ -846,6 +854,7 @@ 4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */, 4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */, 4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */, + 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1197,6 +1206,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/joshuajhomann/Shimmer"; + requirement = { + branch = master; + kind = branch; + }; + }; 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/onevcat/Kingfisher"; diff --git a/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ } }, { + "identity" : "shimmer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/joshuajhomann/Shimmer", + "state" : { + "branch" : "master", + "revision" : "2fde687b3f1d9c5409c53da095d3686361e41343" + } + }, + { "identity" : "starscream", "kind" : "remoteSourceControl", "location" : "https://github.com/daltoniam/Starscream", diff --git a/damus/Components/Shimmer.swift b/damus/Components/Shimmer.swift @@ -0,0 +1,77 @@ +// +// Shimmer.swift +// +// +// Created by Joshua Homann on 2/20/21. +// + +import SwiftUI + +public struct ShimmerConfiguration { + + @Environment(\.colorScheme) var colorScheme + + public let gradient: Gradient + public let initialLocation: (start: UnitPoint, end: UnitPoint) + public let finalLocation: (start: UnitPoint, end: UnitPoint) + public let duration: TimeInterval + public let opacity: Double + public static let `default` = ShimmerConfiguration( + gradient: Gradient(stops: [ + .init(color: .clear, location: 0), + .init(color: .black, location: 0.3), + .init(color: .black, location: 0.7), + .init(color: .clear, location: 1), + ]), + initialLocation: (start: UnitPoint(x: -1, y: 0.5), end: .leading), + finalLocation: (start: .trailing, end: UnitPoint(x: 2, y: 0.5)), + duration: 2, + opacity: 0.6 + ) +} + +struct ShimmeringView<Content: View>: View { + private let content: () -> Content + private let configuration: ShimmerConfiguration + @State private var startPoint: UnitPoint + @State private var endPoint: UnitPoint + init(configuration: ShimmerConfiguration, @ViewBuilder content: @escaping () -> Content) { + self.configuration = configuration + self.content = content + _startPoint = .init(wrappedValue: configuration.initialLocation.start) + _endPoint = .init(wrappedValue: configuration.initialLocation.end) + } + var body: some View { + ZStack { + content() + LinearGradient( + gradient: configuration.gradient, + startPoint: startPoint, + endPoint: endPoint + ) + .opacity(configuration.opacity) + .blendMode(.overlay) + .onAppear { + withAnimation(Animation.linear(duration: configuration.duration).repeatForever(autoreverses: false)) { + startPoint = configuration.finalLocation.start + endPoint = configuration.finalLocation.end + } + } + } + .edgesIgnoringSafeArea(.all) + } +} + +public struct ShimmerModifier: ViewModifier { + let configuration: ShimmerConfiguration + public func body(content: Content) -> some View { + ShimmeringView(configuration: configuration) { content } + } +} + + +public extension View { + func shimmer(configuration: ShimmerConfiguration = .default) -> some View { + modifier(ShimmerModifier(configuration: configuration)) + } +} diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -549,3 +549,4 @@ func setup_notifications() { } } } + diff --git a/damus/Util/Constants.swift b/damus/Util/Constants.swift @@ -0,0 +1,25 @@ +// +// Constants.swift +// damus +// +// Created by Sam DuBois on 12/18/22. +// + +import Foundation + +public class Constants { + + static let PUB_KEY = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681" + + 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"), + ] +} diff --git a/damus/Views/EventActionBar.swift b/damus/Views/EventActionBar.swift @@ -44,7 +44,7 @@ struct EventActionBar: View { HStack(alignment: .bottom) { Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")") - .font(.footnote) + .font(.footnote.weight(.medium)) .foregroundColor(bar.boosted ? Color.green : Color.gray) EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) { @@ -58,7 +58,7 @@ struct EventActionBar: View { HStack(alignment: .bottom) { Text("\(bar.likes > 0 ? "\(bar.likes)" : "")") - .font(.footnote) + .font(.footnote.weight(.medium)) .foregroundColor(bar.liked ? Color.red : Color.gray) EventActionButton(img: bar.liked ? "heart.fill" : "heart", col: bar.liked ? Color.red : nil) { @@ -140,7 +140,7 @@ struct EventActionBar: View { func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View { Button(action: action) { Label("", systemImage: img) - .font(.footnote) + .font(.footnote.weight(.medium)) .foregroundColor(col == nil ? Color.gray : col!) } .padding(.trailing, 40) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift @@ -83,17 +83,22 @@ struct EventView: View { NavigationLink(destination: booster_profile) { HStack { - Label("", systemImage: "arrow.2.squarepath") + Image(systemName: "arrow.2.squarepath") + .font(.footnote.weight(.bold)) .foregroundColor(Color.gray) - ProfileName(pubkey: event.pubkey, profile: damus.profiles.lookup(id: event.pubkey), contacts: damus.contacts, show_friend_confirmed: show_friend_icon) - .foregroundColor(Color.gray) - Text(" Boosted") + if let prof = damus.profiles.lookup(id: event.pubkey) { + Text(Profile.displayName(profile: prof, pubkey: event.pubkey)) + .font(.footnote.weight(.bold)) + .foregroundColor(Color.gray) + } + Text("Boosted") + .font(.footnote.weight(.bold)) .foregroundColor(Color.gray) } } .buttonStyle(PlainButtonStyle()) TextEvent(inner_ev, pubkey: inner_ev.pubkey) - .padding([.top], 2) + .padding([.top], 1) } } else { TextEvent(event, pubkey: pubkey) @@ -119,11 +124,12 @@ struct EventView: View { VStack(alignment: .leading) { HStack(alignment: .center) { - ProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon) + EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon) Text("\(format_relative_time(event.created_at))") + .font(.body) .foregroundColor(.gray) } - + if event.is_reply(damus.keypair.privkey) { Text("\(reply_desc(profiles: damus.profiles, event: event))") .font(.footnote) diff --git a/damus/Views/FollowButtonView.swift b/damus/Views/FollowButtonView.swift @@ -8,6 +8,9 @@ import SwiftUI struct FollowButtonView: View { + + @Environment(\.colorScheme) var colorScheme + let target: FollowTarget @State var follow_state: FollowState @@ -16,15 +19,15 @@ struct FollowButtonView: View { follow_state = perform_follow_btn_action(follow_state, target: target) } label: { Text(follow_btn_txt(follow_state)) - .padding(.horizontal, 20) - .padding(.vertical, 7) + .padding(.horizontal, 25) + .padding(.vertical, 10) .font(.caption.weight(.bold)) - .foregroundColor(follow_state == .unfollows ? .white : .black) - .background(follow_state == .unfollows ? .black : .white) + .foregroundColor(follow_state == .unfollows ? emptyColor() : fillColor()) + .background(follow_state == .unfollows ? fillColor() : emptyColor()) .cornerRadius(20) .overlay { RoundedRectangle(cornerRadius: 16) - .stroke(follow_state == .unfollows ? .white : .gray, lineWidth: 1) + .stroke(follow_state == .unfollows ? .clear : borderColor(), lineWidth: 1) } } .onReceive(handle_notify(.followed)) { notif in @@ -44,6 +47,18 @@ struct FollowButtonView: View { self.follow_state = .unfollows } } + + 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 FollowButtonPreviews: View { diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -64,8 +64,10 @@ struct NoteContentView: View { return VStack(alignment: .leading) { if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) { Text(txt) + .font(.body) } else { Text(artifacts.content) + .font(.body) } if show_images && artifacts.images.count > 0 { ImageCarousel(urls: artifacts.images) diff --git a/damus/Views/ProfileName.swift b/damus/Views/ProfileName.swift @@ -23,7 +23,7 @@ struct ProfileFullName: View { .font(.footnote) .foregroundColor(.gray) } else { - ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true) +// ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true) } } } @@ -73,8 +73,9 @@ struct ProfileName: View { var body: some View { HStack { + Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) - //.foregroundColor(hex_to_rgb(pubkey)) + .font(.body) .fontWeight(prefix == "@" ? .none : .bold) if let frend = friend_icon { Label("", systemImage: frend) @@ -92,4 +93,76 @@ struct ProfileName: View { } } - +/// Profile Name used when displaying an event in the timeline +struct EventProfileName: View { + let pubkey: String + let profile: Profile? + let contacts: Contacts + let prefix: String + + let show_friend_confirmed: Bool + + @State var display_name: String? + + init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) { + self.pubkey = pubkey + self.profile = profile + self.prefix = "" + self.contacts = contacts + self.show_friend_confirmed = show_friend_confirmed + } + + init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) { + self.pubkey = pubkey + self.profile = profile + self.prefix = prefix + self.contacts = contacts + self.show_friend_confirmed = show_friend_confirmed + } + + var friend_icon: String? { + if !show_friend_confirmed { + return nil + } + + if self.contacts.is_friend(self.pubkey) { + return "person.fill.checkmark" + } + + if self.contacts.is_friend_of_friend(self.pubkey) { + return "person.fill.and.arrow.left.and.arrow.right" + } + + return nil + } + + var body: some View { + HStack { + if let real_name = profile?.display_name { + Text(real_name) + .font(.body.weight(.bold)) + + Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) + .foregroundColor(.gray) + .font(.body) + } else { + Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) + .font(.body) + .fontWeight(.bold) + } + + if let frend = friend_icon { + Label("", systemImage: frend) + .foregroundColor(.gray) + .font(.footnote) + } + } + .onReceive(handle_notify(.profile_updated)) { notif in + let update = notif.object as! ProfileUpdate + if update.pubkey != pubkey { + return + } + display_name = Profile.displayName(profile: update.profile, pubkey: pubkey) + } + } +} diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift @@ -95,8 +95,8 @@ struct ProfileView: View { }) { Image(systemName: "bolt.circle") .symbolRenderingMode(.palette) - .foregroundStyle(colorScheme == .dark ? .white : .black, .gray) - .font(.system(size: 27).weight(.thin)) + .font(.system(size: 34).weight(.thin)) + .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)) } } @@ -107,8 +107,8 @@ struct ProfileView: View { return NavigationLink(destination: dmview) { Image(systemName: "bubble.left.circle") .symbolRenderingMode(.palette) - .font(.system(size: 29).weight(.thin)) - .foregroundStyle(colorScheme == .dark ? .white : .black, .gray) + .font(.system(size: 34).weight(.thin)) + .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)) } } @@ -134,6 +134,7 @@ struct ProfileView: View { .padding(.bottom) Text(data?.about ?? "") + .font(.subheadline) Divider() @@ -144,7 +145,9 @@ struct ProfileView: View { NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) { HStack { Text("\(profile.following)") + .font(.subheadline.weight(.medium)) Text("Following") + .font(.subheadline) .foregroundColor(.gray) } } @@ -155,7 +158,9 @@ struct ProfileView: View { NavigationLink(destination: fview) { HStack { Text("\(followers.contacts.count)") + .font(.subheadline.weight(.medium)) Text("Followers") + .font(.subheadline) .foregroundColor(.gray) } } diff --git a/damus/Views/SearchHomeView.swift b/damus/Views/SearchHomeView.swift @@ -40,11 +40,19 @@ struct SearchHomeView: View { } var GlobalContent: some View { - TimelineView(events: $model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true }) + 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 + model.subscribe() + } } var SearchContent: some View { SearchResultsView(damus_state: damus_state, search: $search) + .refreshable { + // Fetch new information by resubscribing to the relay + model.subscribe() + } } var MainContent: some View { diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift @@ -40,7 +40,27 @@ 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] @Binding var loading: Bool @@ -56,6 +76,7 @@ struct TimelineView: View { ScrollViewReader { scroller in ScrollView { if loading { + InnerTimelineRedactedView(events: Constants.EXAMPLE_EVENTS, damus: damus, show_friend_icon: true) ProgressView() .progressViewStyle(.circular) } else { @@ -72,13 +93,11 @@ struct TimelineView: View { } } -/* struct TimelineView_Previews: PreviewProvider { static var previews: some View { - TimelineView() + TimelineView(events: .constant(Constants.EXAMPLE_EVENTS), loading: .constant(true), damus: Constants.EXAMPLE_DEMOS, show_friend_icon: true, filter: { _ in true }) } } - */ struct NavigationLazyView<Content: View>: View { @@ -90,4 +109,3 @@ struct NavigationLazyView<Content: View>: View { build() } } -